package service

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/shankar0123/certctl/internal/domain"
	"github.com/shankar0123/certctl/internal/repository"
)

// AuditService provides business logic for recording and retrieving audit events.
type AuditService struct {
	auditRepo repository.AuditRepository
}

// NewAuditService creates a new audit service.
func NewAuditService(auditRepo repository.AuditRepository) *AuditService {
	return &AuditService{
		auditRepo: auditRepo,
	}
}

// RecordEvent records an audit event with actor, action, and resource information.
func (s *AuditService) RecordEvent(ctx context.Context, actor string, actorType domain.ActorType, action string, resourceType string, resourceID string, details map[string]interface{}) error {
	detailsJSON, err := json.Marshal(details)
	if err != nil {
		detailsJSON = []byte("{}")
	}

	event := &domain.AuditEvent{
		ID:           generateID("audit"),
		Timestamp:    time.Now(),
		Actor:        actor,
		ActorType:    actorType,
		Action:       action,
		ResourceType: resourceType,
		ResourceID:   resourceID,
		Details:      json.RawMessage(detailsJSON),
	}

	if err := s.auditRepo.Create(ctx, event); err != nil {
		return fmt.Errorf("failed to record audit event: %w", err)
	}

	return nil
}

// List returns audit events matching filter criteria.
func (s *AuditService) List(ctx context.Context, filter *repository.AuditFilter) ([]*domain.AuditEvent, error) {
	events, err := s.auditRepo.List(ctx, filter)
	if err != nil {
		return nil, fmt.Errorf("failed to list audit events: %w", err)
	}
	return events, nil
}

// ListByResource returns all audit events for a specific resource.
func (s *AuditService) ListByResource(ctx context.Context, resourceType string, resourceID string) ([]*domain.AuditEvent, error) {
	filter := &repository.AuditFilter{
		ResourceType: resourceType,
		ResourceID:   resourceID,
		PerPage:      1000, // reasonable default for single resource
	}

	events, err := s.auditRepo.List(ctx, filter)
	if err != nil {
		return nil, fmt.Errorf("failed to list audit events: %w", err)
	}
	return events, nil
}

// ListByActor returns all audit events for a specific actor.
func (s *AuditService) ListByActor(ctx context.Context, actor string) ([]*domain.AuditEvent, error) {
	filter := &repository.AuditFilter{
		Actor:   actor,
		PerPage: 1000,
	}

	events, err := s.auditRepo.List(ctx, filter)
	if err != nil {
		return nil, fmt.Errorf("failed to list audit events: %w", err)
	}
	return events, nil
}

// ListByAction returns all audit events for a specific action type.
func (s *AuditService) ListByAction(ctx context.Context, action string, from, to time.Time) ([]*domain.AuditEvent, error) {
	filter := &repository.AuditFilter{
		From:    from,
		To:      to,
		PerPage: 1000,
	}

	events, err := s.auditRepo.List(ctx, filter)
	if err != nil {
		return nil, fmt.Errorf("failed to list audit events: %w", err)
	}

	// Filter by action on client side (repository may not filter by action directly)
	var filtered []*domain.AuditEvent
	for _, e := range events {
		if e.Action == action {
			filtered = append(filtered, e)
		}
	}

	return filtered, nil
}

// ListAuditEvents returns paginated audit events (handler interface method).
func (s *AuditService) ListAuditEvents(page, perPage int) ([]domain.AuditEvent, int64, error) {
	if page < 1 {
		page = 1
	}
	if perPage < 1 {
		perPage = 50
	}

	filter := &repository.AuditFilter{
		Page:    page,
		PerPage: perPage,
	}

	events, err := s.auditRepo.List(context.Background(), filter)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to list audit events: %w", err)
	}

	// Convert pointers to values for the handler interface
	var result []domain.AuditEvent
	for _, e := range events {
		if e != nil {
			result = append(result, *e)
		}
	}

	// TODO: Get total count from repository
	total := int64(len(result))

	return result, total, nil
}

// GetAuditEvent returns a single audit event (handler interface method).
func (s *AuditService) GetAuditEvent(id string) (*domain.AuditEvent, error) {
	filter := &repository.AuditFilter{
		ResourceID: id,
		PerPage:    1,
	}

	events, err := s.auditRepo.List(context.Background(), filter)
	if err != nil {
		return nil, fmt.Errorf("failed to get audit event: %w", err)
	}

	if len(events) == 0 {
		return nil, fmt.Errorf("audit event not found")
	}

	return events[0], nil
}
