package use_cases

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/rwadurian/mpc-system/pkg/logger"
	"github.com/rwadurian/mpc-system/services/session-coordinator/adapters/output/memory"
	"github.com/rwadurian/mpc-system/services/session-coordinator/application/ports/input"
	"github.com/rwadurian/mpc-system/services/session-coordinator/application/ports/output"
	"github.com/rwadurian/mpc-system/services/session-coordinator/domain/entities"
	"github.com/rwadurian/mpc-system/services/session-coordinator/domain/repositories"
	"github.com/rwadurian/mpc-system/services/session-coordinator/domain/services"
	"github.com/rwadurian/mpc-system/services/session-coordinator/domain/value_objects"
	"go.uber.org/zap"
)

// ReportCompletionUseCase implements the report completion use case
type ReportCompletionUseCase struct {
	sessionRepo    repositories.SessionRepository
	eventPublisher output.MessageBrokerPort
	accountService output.AccountServicePort
	coordinatorSvc *services.SessionCoordinatorService
}

// NewReportCompletionUseCase creates a new report completion use case
func NewReportCompletionUseCase(
	sessionRepo repositories.SessionRepository,
	eventPublisher output.MessageBrokerPort,
	accountService output.AccountServicePort,
) *ReportCompletionUseCase {
	return &ReportCompletionUseCase{
		sessionRepo:    sessionRepo,
		eventPublisher: eventPublisher,
		accountService: accountService,
		coordinatorSvc: services.NewSessionCoordinatorService(),
	}
}

const (
	maxRetries = 3 // Maximum number of retry attempts for optimistic lock conflicts
)

// Execute executes the report completion use case with retry logic for optimistic lock conflicts
func (uc *ReportCompletionUseCase) Execute(
	ctx context.Context,
	inputData input.ReportCompletionInput,
) (*input.ReportCompletionOutput, error) {
	return uc.executeWithRetry(ctx, inputData, 0)
}

// executeWithRetry executes the report completion with retry logic
func (uc *ReportCompletionUseCase) executeWithRetry(
	ctx context.Context,
	inputData input.ReportCompletionInput,
	retry int,
) (*input.ReportCompletionOutput, error) {
	if retry >= maxRetries {
		logger.Error("max retries exceeded for optimistic lock",
			zap.String("session_id", inputData.SessionID.String()),
			zap.String("party_id", inputData.PartyID),
			zap.Int("retry_count", retry))
		return nil, fmt.Errorf("max retries exceeded: %w", entities.ErrOptimisticLockConflict)
	}

	if retry > 0 {
		logger.Info("retrying report completion due to optimistic lock conflict",
			zap.String("session_id", inputData.SessionID.String()),
			zap.String("party_id", inputData.PartyID),
			zap.Int("retry_attempt", retry))
	}

	logger.Debug("ReportCompletion.Execute: START",
		zap.String("session_id", inputData.SessionID.String()),
		zap.String("party_id", inputData.PartyID),
		zap.Int("retry", retry))

	// 1. Load session
	session, err := uc.sessionRepo.FindByUUID(ctx, inputData.SessionID)
	if err != nil {
		return nil, err
	}

	// DEBUG: Log all participant statuses after loading session
	var allParticipantStatuses []string
	for _, p := range session.Participants {
		allParticipantStatuses = append(allParticipantStatuses, p.PartyID.String()+":"+p.Status.String())
	}
	logger.Debug("ReportCompletion.Execute: loaded session",
		zap.String("session_id", session.ID.String()),
		zap.String("party_id", inputData.PartyID),
		zap.Strings("all_participant_statuses", allParticipantStatuses))

	// 2. Create party ID value object
	partyID, err := value_objects.NewPartyID(inputData.PartyID)
	if err != nil {
		return nil, err
	}

	// 3. Get participant and check current status
	participant, err := session.GetParticipant(partyID)
	if err != nil {
		return nil, err
	}

	logger.Debug("ReportCompletion.Execute: current participant status",
		zap.String("session_id", session.ID.String()),
		zap.String("party_id", inputData.PartyID),
		zap.String("current_status", participant.Status.String()))

	// 3.1 Ensure participant is in Ready state before marking as Completed
	// The status flow is: Invited -> Joined -> Ready -> Completed
	// Handle all possible states to reach Ready
	switch participant.Status {
	case value_objects.ParticipantStatusInvited:
		// Invited -> Joined
		if err := session.UpdateParticipantStatus(partyID, value_objects.ParticipantStatusJoined); err != nil {
			return nil, err
		}
		logger.Debug("auto-transitioned participant from invited to joined",
			zap.String("session_id", session.ID.String()),
			zap.String("party_id", inputData.PartyID))
		// Joined -> Ready
		if err := session.UpdateParticipantStatus(partyID, value_objects.ParticipantStatusReady); err != nil {
			return nil, err
		}
		logger.Debug("auto-transitioned participant from joined to ready",
			zap.String("session_id", session.ID.String()),
			zap.String("party_id", inputData.PartyID))
	case value_objects.ParticipantStatusJoined:
		// Joined -> Ready
		if err := session.UpdateParticipantStatus(partyID, value_objects.ParticipantStatusReady); err != nil {
			return nil, err
		}
		logger.Debug("auto-transitioned participant from joined to ready",
			zap.String("session_id", session.ID.String()),
			zap.String("party_id", inputData.PartyID))
	case value_objects.ParticipantStatusReady:
		// Already ready, no transition needed
	default:
		// Invalid state for completion (Failed, Completed, etc.)
		return nil, fmt.Errorf("participant in invalid state for completion: %s", participant.Status)
	}

	// 3.2 Update participant status to completed
	if err := session.UpdateParticipantStatus(partyID, value_objects.ParticipantStatusCompleted); err != nil {
		return nil, err
	}

	// 3.1 Update party activity timestamp (party is still active)
	if err := session.UpdatePartyActivity(partyID); err != nil {
		logger.Warn("failed to update party activity",
			zap.String("session_id", session.ID.String()),
			zap.String("party_id", inputData.PartyID),
			zap.Error(err))
	}

	// 4. Update participant's public key if provided
	if len(inputData.PublicKey) > 0 {
		participant.SetPublicKey(inputData.PublicKey)
	}

	// 5. Check if all participants have completed
	allCompleted := uc.coordinatorSvc.ShouldCompleteSession(session)
	if allCompleted {
		// Use the public key from the input (all participants should have the same public key after keygen)
		if err := session.Complete(inputData.PublicKey); err != nil {
			return nil, err
		}

		// For sign sessions, cache the signature for HTTP API retrieval
		// Note: server-party sends signature in PublicKey field (same field used for keygen public key)
		if session.SessionType == entities.SessionTypeSign && len(inputData.PublicKey) > 0 {
			memory.GetSignatureCache().Set(session.ID.UUID(), inputData.PublicKey)
			logger.Info("cached signature for sign session",
				zap.String("session_id", session.ID.String()),
				zap.Int("signature_len", len(inputData.PublicKey)))
		}

		// Publish session completed event
		completedEvent := output.SessionCompletedEvent{
			SessionID:   session.ID.String(),
			PublicKey:   session.PublicKey,
			CompletedAt: time.Now().UnixMilli(),
		}
		if err := uc.eventPublisher.PublishEvent(ctx, output.TopicSessionCompleted, completedEvent); err != nil {
			logger.Error("failed to publish session completed event",
				zap.String("session_id", session.ID.String()),
				zap.Error(err))
		}

		// For keygen sessions (including co_managed_keygen), automatically create account record
		if session.SessionType.IsKeygen() && uc.accountService != nil {
			uc.createAccountFromKeygen(ctx, session)
		}
	}

	// 6. Save updated session
	// DEBUG: Log all participant statuses before calling Update
	var beforeUpdateStatuses []string
	for _, p := range session.Participants {
		beforeUpdateStatuses = append(beforeUpdateStatuses, p.PartyID.String()+":"+p.Status.String())
	}
	logger.Debug("ReportCompletion.Execute: BEFORE sessionRepo.Update",
		zap.String("session_id", session.ID.String()),
		zap.String("party_id", inputData.PartyID),
		zap.Strings("all_participant_statuses", beforeUpdateStatuses))

	if err := uc.sessionRepo.Update(ctx, session); err != nil {
		// Check if this is an optimistic lock conflict
		if errors.Is(err, entities.ErrOptimisticLockConflict) {
			logger.Warn("optimistic lock conflict, retrying",
				zap.String("session_id", session.ID.String()),
				zap.String("party_id", inputData.PartyID),
				zap.Int("retry", retry))
			// Retry the entire operation with fresh data
			return uc.executeWithRetry(ctx, inputData, retry+1)
		}
		return nil, err
	}

	logger.Debug("ReportCompletion.Execute: AFTER sessionRepo.Update",
		zap.String("session_id", session.ID.String()),
		zap.String("party_id", inputData.PartyID),
		zap.Int("retry", retry))

	// 7. Publish participant completed event
	event := output.ParticipantCompletedEvent{
		SessionID:   session.ID.String(),
		PartyID:     inputData.PartyID,
		CompletedAt: time.Now().UnixMilli(),
	}
	if err := uc.eventPublisher.PublishEvent(ctx, output.TopicParticipantCompleted, event); err != nil {
		logger.Error("failed to publish participant completed event",
			zap.String("session_id", session.ID.String()),
			zap.String("party_id", inputData.PartyID),
			zap.Error(err))
	}

	return &input.ReportCompletionOutput{
		Success:      true,
		AllCompleted: allCompleted,
	}, nil
}

// createAccountFromKeygen creates an account record after successful keygen
func (uc *ReportCompletionUseCase) createAccountFromKeygen(ctx context.Context, session *entities.MPCSession) {
	// Build share info from participants
	shares := make([]output.ShareInfo, 0, len(session.Participants))
	for _, p := range session.Participants {
		shareType := "persistent"
		if p.PartyID.String() == session.DelegatePartyID {
			shareType = "delegate"
		}
		shares = append(shares, output.ShareInfo{
			PartyID:    p.PartyID.String(),
			PartyIndex: p.PartyIndex,
			ShareType:  shareType,
		})
	}

	// Call account service to create account
	accountInput := output.CreateAccountInput{
		KeygenSessionID: session.ID.String(),
		PublicKey:       session.PublicKey,
		ThresholdN:      session.Threshold.N(),
		ThresholdT:      session.Threshold.T(),
		Shares:          shares,
		DelegatePartyID: session.DelegatePartyID,
	}

	result, err := uc.accountService.CreateAccountFromKeygen(ctx, accountInput)
	if err != nil {
		logger.Error("failed to create account from keygen",
			zap.String("session_id", session.ID.String()),
			zap.Error(err))
		return
	}

	logger.Info("account created from keygen",
		zap.String("session_id", session.ID.String()),
		zap.String("account_id", result.AccountID))
}
