package services

import (
	"context"
	"time"

	"github.com/rwadurian/mpc-system/services/session-coordinator/domain/entities"
	"github.com/rwadurian/mpc-system/services/session-coordinator/domain/value_objects"
)

// SessionCoordinatorService is the domain service for session coordination
type SessionCoordinatorService struct{}

// NewSessionCoordinatorService creates a new session coordinator service
func NewSessionCoordinatorService() *SessionCoordinatorService {
	return &SessionCoordinatorService{}
}

// ValidateSessionCreation validates session creation parameters
func (s *SessionCoordinatorService) ValidateSessionCreation(
	sessionType entities.SessionType,
	threshold value_objects.Threshold,
	participantCount int,
	messageHash []byte,
) error {
	if !sessionType.IsValid() {
		return entities.ErrInvalidSessionType
	}

	// Validate participant count based on session type
	// For keygen: all n parties must participate (participantCount == n or 0 for dynamic)
	// For sign: at least t parties required, can have up to n (participantCount >= t && <= n)
	//   - Co-managed sign uses exactly T parties
	//   - Persistent sign uses T+1 parties
	//
	// BREAKING CHANGE WARNING (for co-sign feature, commit 94ab63db):
	// Original code: participantCount == threshold.N() for ALL session types
	// New code: T <= participantCount <= N for sign sessions
	// This change affects PERSISTENT SIGN flow because we now pass keygenThresholdN
	// instead of len(parties) as threshold_n in CreateSigningSessionAuto.
	// If issues arise with persistent sign, REVERT to: participantCount == threshold.N()
	// Related files: session_coordinator_client.go, account_handler.go, mpc_session.go
	if participantCount != 0 {
		if sessionType == entities.SessionTypeSign {
			// Signing session: participant count must be at least t (threshold)
			// and at most n (total parties from keygen)
			if participantCount < threshold.T() || participantCount > threshold.N() {
				return entities.ErrSessionFull
			}
		} else {
			// Keygen session: participant count should equal threshold n
			// (all parties must participate in key generation)
			if participantCount != threshold.N() {
				return entities.ErrSessionFull
			}
		}
	}

	if sessionType == entities.SessionTypeSign && len(messageHash) == 0 {
		return ErrMessageHashRequired
	}

	return nil
}

// CanParticipantJoin checks if a participant can join a session
func (s *SessionCoordinatorService) CanParticipantJoin(
	session *entities.MPCSession,
	partyID value_objects.PartyID,
) error {
	if session.IsExpired() {
		return entities.ErrSessionExpired
	}

	if !session.Status.IsActive() {
		return ErrSessionNotActive
	}

	if !session.IsParticipant(partyID) {
		return ErrNotAParticipant
	}

	participant, err := session.GetParticipant(partyID)
	if err != nil {
		return err
	}

	if participant.IsJoined() {
		return ErrAlreadyJoined
	}

	return nil
}

// ShouldStartSession determines if a session should start
func (s *SessionCoordinatorService) ShouldStartSession(session *entities.MPCSession) bool {
	return session.Status == value_objects.SessionStatusCreated && session.CanStart()
}

// ShouldCompleteSession determines if a session should be marked as completed
func (s *SessionCoordinatorService) ShouldCompleteSession(session *entities.MPCSession) bool {
	return session.Status == value_objects.SessionStatusInProgress && session.AllCompleted()
}

// ShouldExpireSession determines if a session should be expired
func (s *SessionCoordinatorService) ShouldExpireSession(session *entities.MPCSession) bool {
	return session.IsExpired() && !session.Status.IsTerminal()
}

// CalculateSessionTimeout calculates the timeout for a session type
// Sessions are valid for 24 hours to allow users sufficient time to complete MPC operations
// Note: This is separate from reliability timeouts (party heartbeat/activity) which remain at 2 minutes
func (s *SessionCoordinatorService) CalculateSessionTimeout(sessionType entities.SessionType) time.Duration {
	// All session types have a 24-hour validity period
	return 24 * time.Hour
}

// ValidateMessageRouting validates if a message can be routed
func (s *SessionCoordinatorService) ValidateMessageRouting(
	ctx context.Context,
	session *entities.MPCSession,
	fromParty value_objects.PartyID,
	toParties []value_objects.PartyID,
) error {
	if session.Status != value_objects.SessionStatusInProgress {
		return entities.ErrSessionNotInProgress
	}

	if !session.IsParticipant(fromParty) {
		return ErrNotAParticipant
	}

	// Validate all target parties are participants
	for _, toParty := range toParties {
		if !session.IsParticipant(toParty) {
			return ErrInvalidTargetParty
		}
	}

	return nil
}

// Domain service errors
var (
	ErrMessageHashRequired = &DomainError{Code: "MESSAGE_HASH_REQUIRED", Message: "message hash is required for sign sessions"}
	ErrSessionNotActive    = &DomainError{Code: "SESSION_NOT_ACTIVE", Message: "session is not active"}
	ErrNotAParticipant     = &DomainError{Code: "NOT_A_PARTICIPANT", Message: "not a participant in this session"}
	ErrAlreadyJoined       = &DomainError{Code: "ALREADY_JOINED", Message: "participant has already joined"}
	ErrInvalidTargetParty  = &DomainError{Code: "INVALID_TARGET_PARTY", Message: "invalid target party"}
)

// DomainError represents a domain-specific error
type DomainError struct {
	Code    string
	Message string
}

func (e *DomainError) Error() string {
	return e.Message
}
