package approvalapp

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/approval"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

type SubjectCancelPolicy string

const (
	SubjectCancelNotSupported SubjectCancelPolicy = "not_supported"
	SubjectCancelByApplicant  SubjectCancelPolicy = "applicant"
)

type SubjectReconcilePolicy string

const (
	SubjectReconcileNone       SubjectReconcilePolicy = "none"
	SubjectReconcileProjection SubjectReconcilePolicy = "projection"
)

// SubjectPrincipal is the transport-neutral, already-authenticated actor.
// Subject payloads never supply or override these identity facts.
type SubjectPrincipal struct {
	ActorType   string
	WorkspaceID string
	PrincipalID string
	MemberID    string
	MachineID   string
}

type SubmitCommand struct {
	SubjectType     string
	Principal       SubjectPrincipal
	SubjectRef      string
	ClientRequestID string
	Payload         any
	CorrelationID   string
	Now             time.Time
}

type SubmitResult struct {
	Instance    approval.Instance
	Assignments []approval.Assignment
	Created     bool
	Value       any
}

type DecisionCommand struct {
	WorkspaceID      string
	ApprovalID       string
	AssignmentID     string
	ActorMemberID    string
	ExpectedVersion  int64
	ClientDecisionID string
	Decision         string
	Reason           string
	CorrelationID    string
	Now              time.Time
}

type CancelCommand struct {
	Principal       SubjectPrincipal
	ApprovalID      string
	ExpectedVersion int64
	ClientRequestID string
	Now             time.Time
}

type DecisionResult struct {
	Instance approval.Instance
	Value    any
	Replay   bool
	Result   map[string]any
}

// PresentationContext contains only App notification transport facts.
// Domain decision/apply handlers never receive the original App envelope.
type PresentationContext struct {
	NotificationID string
	DeliveryID     string
	AttemptID      string
	MutationID     string
}

// SubjectHandler owns the transport-neutral decision/apply boundary.
type SubjectHandler interface {
	SubjectType() string
	CancelPolicy() SubjectCancelPolicy
	ReconcilePolicy() SubjectReconcilePolicy
	Validate(context.Context, approval.Instance) error
	Decide(context.Context, DecisionCommand) (DecisionResult, error)
}

type SubjectSubmitter interface {
	RequestType() string
	DecodeSubmitPayload(any) (any, error)
	Submit(context.Context, SubmitCommand) (SubmitResult, error)
}

// SubjectReplayLookup optionally exposes a read-only idempotency lookup. It is
// used when a request's mutable preflight resources may no longer exist after
// the original approval has reached a terminal state.
type SubjectReplayLookup interface {
	LookupReplay(context.Context, SubmitCommand) (SubmitResult, bool, error)
}

type SubjectCanceller interface {
	Cancel(context.Context, CancelCommand) (DecisionResult, error)
}

type SubjectPendingPresenter interface {
	PresentPending(context.Context, SubmitResult) (app.AppNotificationResult, error)
}

type SubjectResolvedPresenter interface {
	PresentResolved(context.Context, DecisionResult, PresentationContext) (app.AppNotificationResult, error)
}

type SubjectInstanceLoader interface {
	Load(context.Context, string, string) (approval.Instance, error)
	MarkProjected(context.Context, string, string, int64) error
}

type SubjectRegistry struct {
	mu        sync.RWMutex
	handlers  map[string]SubjectHandler
	requests  map[string]string
	instances SubjectInstanceLoader
	sealed    bool
}

func NewSubjectRegistry(instances ...SubjectInstanceLoader) *SubjectRegistry {
	registry := &SubjectRegistry{
		handlers: make(map[string]SubjectHandler),
		requests: make(map[string]string),
	}
	if len(instances) != 0 {
		registry.instances = instances[0]
	}
	return registry
}

func sealedEmptySubjectRegistry() *SubjectRegistry {
	registry := NewSubjectRegistry()
	registry.Seal()
	return registry
}

func (registry *SubjectRegistry) Register(handler SubjectHandler) error {
	if registry == nil || handler == nil {
		return fmt.Errorf("Approval subject handler is required")
	}
	subjectType := strings.TrimSpace(handler.SubjectType())
	if subjectType == "" {
		return fmt.Errorf("Approval subject handler type is required")
	}
	registry.mu.Lock()
	defer registry.mu.Unlock()
	if registry.sealed {
		return fmt.Errorf("Approval subject registry is sealed")
	}
	if _, exists := registry.handlers[subjectType]; exists {
		return fmt.Errorf("Approval subject handler %q is already registered", subjectType)
	}
	if submitter, ok := handler.(SubjectSubmitter); ok {
		requestType := strings.TrimSpace(submitter.RequestType())
		if requestType == "" {
			return fmt.Errorf("Approval request type is required")
		}
		if _, exists := registry.requests[requestType]; exists {
			return fmt.Errorf("Approval request type %q is already registered", requestType)
		}
		registry.requests[requestType] = subjectType
	}
	registry.handlers[subjectType] = handler
	return nil
}

func (registry *SubjectRegistry) Seal() {
	registry.mu.Lock()
	registry.sealed = true
	registry.mu.Unlock()
}

func (registry *SubjectRegistry) resolve(subjectType string) (SubjectHandler, error) {
	if registry == nil {
		return nil, fmt.Errorf("Approval subject registry is not configured")
	}
	registry.mu.RLock()
	defer registry.mu.RUnlock()
	if !registry.sealed {
		return nil, fmt.Errorf("Approval subject registry is not sealed")
	}
	handler, ok := registry.handlers[strings.TrimSpace(subjectType)]
	if !ok {
		return nil, fmt.Errorf("Approval subject type %q is not registered", subjectType)
	}
	return handler, nil
}

func (registry *SubjectRegistry) Submit(ctx context.Context, command SubmitCommand) (SubmitResult, error) {
	handler, err := registry.resolve(command.SubjectType)
	if err != nil {
		return SubmitResult{}, err
	}
	submitter, ok := handler.(SubjectSubmitter)
	if !ok {
		return SubmitResult{}, fmt.Errorf("Approval subject type %q does not support submit", command.SubjectType)
	}
	payload, err := submitter.DecodeSubmitPayload(command.Payload)
	if err != nil {
		return SubmitResult{}, err
	}
	command.Payload = payload
	return submitter.Submit(ctx, command)
}

func (registry *SubjectRegistry) SubmitRequest(
	ctx context.Context,
	requestType string,
	command SubmitCommand,
) (SubmitResult, error) {
	if registry == nil {
		return SubmitResult{}, fmt.Errorf("Approval subject registry is not configured")
	}
	registry.mu.RLock()
	if !registry.sealed {
		registry.mu.RUnlock()
		return SubmitResult{}, fmt.Errorf("Approval subject registry is not sealed")
	}
	subjectType, ok := registry.requests[strings.TrimSpace(requestType)]
	registry.mu.RUnlock()
	if !ok {
		return SubmitResult{}, storeerr.BadRequest(
			fmt.Sprintf("Approval request type %q is not registered", requestType),
		)
	}
	command.SubjectType = subjectType
	return registry.Submit(ctx, command)
}

// LookupRequestReplay performs a read-only lookup for one request type. It
// never calls Submit and therefore cannot create an approval on a miss.
func (registry *SubjectRegistry) LookupRequestReplay(
	ctx context.Context,
	requestType string,
	command SubmitCommand,
) (SubmitResult, bool, error) {
	if registry == nil {
		return SubmitResult{}, false, fmt.Errorf("Approval subject registry is not configured")
	}
	registry.mu.RLock()
	if !registry.sealed {
		registry.mu.RUnlock()
		return SubmitResult{}, false, fmt.Errorf("Approval subject registry is not sealed")
	}
	subjectType, ok := registry.requests[strings.TrimSpace(requestType)]
	registry.mu.RUnlock()
	if !ok {
		return SubmitResult{}, false, storeerr.BadRequest(
			fmt.Sprintf("Approval request type %q is not registered", requestType),
		)
	}
	handler, err := registry.resolve(subjectType)
	if err != nil {
		return SubmitResult{}, false, err
	}
	submitter, ok := handler.(SubjectSubmitter)
	if !ok {
		return SubmitResult{}, false, fmt.Errorf("Approval subject type %q does not support submit", subjectType)
	}
	replay, ok := handler.(SubjectReplayLookup)
	if !ok {
		return SubmitResult{}, false, nil
	}
	payload, err := submitter.DecodeSubmitPayload(command.Payload)
	if err != nil {
		return SubmitResult{}, false, err
	}
	command.SubjectType = subjectType
	command.Payload = payload
	return replay.LookupReplay(ctx, command)
}

func (registry *SubjectRegistry) Decide(
	ctx context.Context,
	command DecisionCommand,
) (DecisionResult, error) {
	instance, err := registry.loadAuthoritative(
		ctx, command.WorkspaceID, command.ApprovalID,
	)
	if err != nil {
		return DecisionResult{}, err
	}
	handler, err := registry.resolve(instance.SubjectType)
	if err != nil {
		return DecisionResult{}, err
	}
	if err := handler.Validate(ctx, instance); err != nil {
		return DecisionResult{}, err
	}
	return handler.Decide(ctx, command)
}

func (registry *SubjectRegistry) Cancel(
	ctx context.Context,
	command CancelCommand,
) (DecisionResult, error) {
	instance, err := registry.loadAuthoritative(
		ctx, command.Principal.WorkspaceID, command.ApprovalID,
	)
	if err != nil {
		return DecisionResult{}, err
	}
	handler, err := registry.resolve(instance.SubjectType)
	if err != nil {
		return DecisionResult{}, err
	}
	if err := handler.Validate(ctx, instance); err != nil {
		return DecisionResult{}, err
	}
	if handler.CancelPolicy() != SubjectCancelByApplicant {
		return DecisionResult{}, fmt.Errorf("Approval subject type %q does not support cancellation", instance.SubjectType)
	}
	canceller, ok := handler.(SubjectCanceller)
	if !ok {
		return DecisionResult{}, fmt.Errorf("Approval subject type %q cancellation handler is not configured", instance.SubjectType)
	}
	return canceller.Cancel(ctx, command)
}

func (registry *SubjectRegistry) loadAuthoritative(
	ctx context.Context,
	workspaceID, approvalID string,
) (approval.Instance, error) {
	if registry == nil || registry.instances == nil {
		return approval.Instance{}, fmt.Errorf(
			"Approval subject authority is not configured",
		)
	}
	workspaceID = strings.TrimSpace(workspaceID)
	approvalID = strings.TrimSpace(approvalID)
	if workspaceID == "" || approvalID == "" {
		return approval.Instance{}, fmt.Errorf(
			"Approval subject workspace and instance are required",
		)
	}
	instance, err := registry.instances.Load(ctx, workspaceID, approvalID)
	if err != nil {
		return approval.Instance{}, err
	}
	if instance.WorkspaceID != workspaceID || instance.ID != approvalID {
		return approval.Instance{}, fmt.Errorf(
			"Approval subject authority returned a mismatched instance",
		)
	}
	return instance, nil
}

func (registry *SubjectRegistry) PresentPending(ctx context.Context, result SubmitResult) (app.AppNotificationResult, error) {
	handler, err := registry.resolve(result.Instance.SubjectType)
	if err != nil {
		return app.AppNotificationResult{}, err
	}
	presenter, ok := handler.(SubjectPendingPresenter)
	if !ok {
		return app.AppNotificationResult{}, fmt.Errorf("Approval subject type %q has no presenter", result.Instance.SubjectType)
	}
	projected, err := presenter.PresentPending(ctx, result)
	if err != nil {
		return app.AppNotificationResult{}, err
	}
	if err := registry.markProjection(ctx, result.Instance); err != nil {
		return app.AppNotificationResult{}, err
	}
	return projected, nil
}

func (registry *SubjectRegistry) PresentResolved(ctx context.Context, result DecisionResult, presentation PresentationContext) (app.AppNotificationResult, error) {
	handler, err := registry.resolve(result.Instance.SubjectType)
	if err != nil {
		return app.AppNotificationResult{}, err
	}
	presenter, ok := handler.(SubjectResolvedPresenter)
	if !ok {
		return app.AppNotificationResult{}, fmt.Errorf("Approval subject type %q has no presenter", result.Instance.SubjectType)
	}
	projected, err := presenter.PresentResolved(ctx, result, presentation)
	if err != nil {
		return app.AppNotificationResult{}, err
	}
	if err := registry.markProjection(ctx, result.Instance); err != nil {
		return app.AppNotificationResult{}, err
	}
	return projected, nil
}

func (registry *SubjectRegistry) markProjection(
	ctx context.Context,
	instance approval.Instance,
) error {
	if registry == nil || registry.instances == nil {
		return fmt.Errorf("Approval subject authority is not configured")
	}
	return registry.instances.MarkProjected(
		ctx, instance.WorkspaceID, instance.ID, instance.Version,
	)
}

func (registry *SubjectRegistry) ReconcileTypes() []string {
	if registry == nil {
		return nil
	}
	registry.mu.RLock()
	defer registry.mu.RUnlock()
	result := make([]string, 0, len(registry.handlers))
	for subjectType, handler := range registry.handlers {
		if handler.ReconcilePolicy() == SubjectReconcileProjection {
			result = append(result, subjectType)
		}
	}
	sort.Strings(result)
	return result
}

func (registry *SubjectRegistry) RegisteredTypes() []string {
	if registry == nil {
		return nil
	}
	registry.mu.RLock()
	defer registry.mu.RUnlock()
	result := make([]string, 0, len(registry.handlers))
	for subjectType := range registry.handlers {
		result = append(result, subjectType)
	}
	sort.Strings(result)
	return result
}
