package audit

import (
	"context"
	"fmt"
	"strings"

	platformregistry "github.com/gpuaas/platform/packages/platform/registry"
)

type Backend interface {
	Append(ctx context.Context, input AppendInput) (*Event, error)
	Query(ctx context.Context, query Query) (*Page, error)
}

type TransactionalBackend interface {
	AppendTx(ctx context.Context, tx any, input AppendInput) (*Event, error)
}

type Service struct {
	backend             Backend
	auditActionRegistry AuditActionRegistry
}

type AuditActionRegistry interface {
	GetAuditAction(ctx context.Context, actionID string) (*platformregistry.AuditAction, error)
}

func NewService(backend Backend) *Service {
	return &Service{backend: backend}
}

func NewServiceWithAuditActionRegistry(backend Backend, auditActionRegistry AuditActionRegistry) *Service {
	return &Service{backend: backend, auditActionRegistry: auditActionRegistry}
}

func (s *Service) Append(ctx context.Context, input AppendInput) (*Event, error) {
	input = normalizeAppendInput(input)
	if err := validateAppendInput(input); err != nil {
		return nil, err
	}
	if err := s.validateAuditAction(ctx, input); err != nil {
		return nil, err
	}
	if s == nil || s.backend == nil {
		return nil, ErrUnavailable
	}
	return s.backend.Append(ctx, input)
}

func (s *Service) AppendTx(ctx context.Context, tx any, input AppendInput) (*Event, error) {
	input = normalizeAppendInput(input)
	if err := validateAppendInput(input); err != nil {
		return nil, err
	}
	if err := s.validateAuditAction(ctx, input); err != nil {
		return nil, err
	}
	if s == nil || s.backend == nil {
		return nil, ErrUnavailable
	}
	txBackend, ok := s.backend.(TransactionalBackend)
	if !ok {
		return nil, ErrUnavailable
	}
	return txBackend.AppendTx(ctx, tx, input)
}

func (s *Service) Query(ctx context.Context, query Query) (*Page, error) {
	query = normalizeQuery(query)
	if s == nil || s.backend == nil {
		return nil, ErrUnavailable
	}
	return s.backend.Query(ctx, query)
}

func (s *Service) validateAuditAction(ctx context.Context, input AppendInput) error {
	if s == nil || s.auditActionRegistry == nil {
		return nil
	}
	action, err := s.auditActionRegistry.GetAuditAction(ctx, input.Action)
	if err != nil {
		return fmt.Errorf("%w: action_id=%s: %v", ErrAuditActionUnavailable, input.Action, err)
	}
	if action.Lifecycle != platformregistry.LifecycleActive {
		return fmt.Errorf("%w: action_id=%s lifecycle=%s", ErrAuditActionInactive, input.Action, action.Lifecycle)
	}
	if strings.TrimSpace(action.TargetType) != strings.TrimSpace(input.TargetType) {
		return fmt.Errorf("%w: action_id=%s registry_target=%s input_target=%s", ErrAuditActionTargetMismatch, input.Action, action.TargetType, input.TargetType)
	}
	return nil
}

func normalizeAppendInput(input AppendInput) AppendInput {
	input.ActorRole = strings.TrimSpace(input.ActorRole)
	input.Action = strings.TrimSpace(input.Action)
	input.TargetType = strings.TrimSpace(input.TargetType)
	input.Result = strings.TrimSpace(input.Result)
	input.CorrelationID = strings.TrimSpace(input.CorrelationID)
	input.OrgID = trimStringPtr(input.OrgID)
	input.ActorUserID = trimStringPtr(input.ActorUserID)
	input.ActorServiceAccountID = trimStringPtr(input.ActorServiceAccountID)
	if input.TargetID != nil {
		trimmed := strings.TrimSpace(*input.TargetID)
		input.TargetID = &trimmed
	}
	return input
}

func validateAppendInput(input AppendInput) error {
	if input.ActorRole == "" || input.Action == "" || input.TargetType == "" || input.Result == "" || input.CorrelationID == "" {
		return ErrInvalidInput
	}
	if !validActorRole(input.ActorRole) || !validResult(input.Result) {
		return ErrInvalidInput
	}
	return nil
}

func normalizeQuery(query Query) Query {
	if query.PageSize <= 0 {
		query.PageSize = 50
	}
	maxPageSize := query.MaxPageSize
	if maxPageSize <= 0 {
		maxPageSize = 100
	}
	if query.PageSize > maxPageSize {
		query.PageSize = maxPageSize
	}
	query.Sort = strings.TrimSpace(query.Sort)
	if query.Sort == "" {
		query.Sort = SortOccurredAtDesc
	}
	query.Cursor = strings.TrimSpace(query.Cursor)
	query.ActorUserID = trimStringPtr(query.ActorUserID)
	query.OrgID = trimStringPtr(query.OrgID)
	query.ProjectID = trimStringPtr(query.ProjectID)
	query.TargetType = trimStringPtr(query.TargetType)
	query.TargetID = trimStringPtr(query.TargetID)
	query.Result = trimStringPtr(query.Result)
	query.CorrelationID = trimStringPtr(query.CorrelationID)
	query.ActorRole = trimStringSlice(query.ActorRole)
	query.Action = trimStringSlice(query.Action)
	query.ActionMatch = strings.TrimSpace(query.ActionMatch)
	if query.ActionMatch == "" {
		query.ActionMatch = ActionMatchExact
	}
	query.Mode = strings.TrimSpace(query.Mode)
	if query.Mode == "" {
		query.Mode = QueryModeGeneric
	}
	return query
}

func validActorRole(role string) bool {
	switch role {
	case ActorRoleUser, ActorRoleAdmin, ActorRoleServiceAccount, ActorRoleSystem:
		return true
	default:
		return false
	}
}

func validResult(result string) bool {
	switch result {
	case ResultSuccess, ResultFailure:
		return true
	default:
		return false
	}
}

func trimStringPtr(value *string) *string {
	if value == nil {
		return nil
	}
	trimmed := strings.TrimSpace(*value)
	if trimmed == "" {
		return nil
	}
	return &trimmed
}

func trimStringSlice(values []string) []string {
	out := make([]string, 0, len(values))
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed != "" {
			out = append(out, trimmed)
		}
	}
	return out
}
