package entities

import (
	"errors"
	"time"

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

var (
	ErrInvalidDeviceInfo     = errors.New("invalid device info")
	ErrParticipantNotInvited = errors.New("participant not in invited status")
	ErrInvalidParticipant    = errors.New("invalid participant")
)

// Participant represents a party in an MPC session
type Participant struct {
	PartyID        value_objects.PartyID
	PartyIndex     int
	Status         value_objects.ParticipantStatus
	DeviceInfo     DeviceInfo
	PublicKey      []byte     // Party's identity public key (for authentication)
	JoinedAt       time.Time
	CompletedAt    *time.Time
	LastActivityAt time.Time  // Last activity timestamp for timeout detection
}

// NewParticipant creates a new participant
func NewParticipant(partyID value_objects.PartyID, partyIndex int, deviceInfo DeviceInfo) (*Participant, error) {
	if partyID.IsZero() {
		return nil, ErrInvalidParticipant
	}
	if partyIndex < 0 {
		return nil, ErrInvalidParticipant
	}
	if err := deviceInfo.Validate(); err != nil {
		return nil, err
	}

	return &Participant{
		PartyID:    partyID,
		PartyIndex: partyIndex,
		Status:     value_objects.ParticipantStatusInvited,
		DeviceInfo: deviceInfo,
		JoinedAt:   time.Now().UTC(),
	}, nil
}

// Join marks the participant as joined
func (p *Participant) Join() error {
	if !p.Status.CanTransitionTo(value_objects.ParticipantStatusJoined) {
		return errors.New("cannot transition to joined status")
	}
	p.Status = value_objects.ParticipantStatusJoined
	p.JoinedAt = time.Now().UTC()
	return nil
}

// MarkReady marks the participant as ready
func (p *Participant) MarkReady() error {
	if !p.Status.CanTransitionTo(value_objects.ParticipantStatusReady) {
		return errors.New("cannot transition to ready status")
	}
	p.Status = value_objects.ParticipantStatusReady
	return nil
}

// MarkCompleted marks the participant as completed
func (p *Participant) MarkCompleted() error {
	if !p.Status.CanTransitionTo(value_objects.ParticipantStatusCompleted) {
		return errors.New("cannot transition to completed status")
	}
	p.Status = value_objects.ParticipantStatusCompleted
	now := time.Now().UTC()
	p.CompletedAt = &now
	return nil
}

// MarkFailed marks the participant as failed
func (p *Participant) MarkFailed() {
	p.Status = value_objects.ParticipantStatusFailed
}

// IsJoined checks if the participant has joined
func (p *Participant) IsJoined() bool {
	return p.Status == value_objects.ParticipantStatusJoined ||
		p.Status == value_objects.ParticipantStatusReady ||
		p.Status == value_objects.ParticipantStatusCompleted
}

// IsReady checks if the participant is ready
func (p *Participant) IsReady() bool {
	return p.Status == value_objects.ParticipantStatusReady ||
		p.Status == value_objects.ParticipantStatusCompleted
}

// IsCompleted checks if the participant has completed
func (p *Participant) IsCompleted() bool {
	return p.Status == value_objects.ParticipantStatusCompleted
}

// IsFailed checks if the participant has failed
func (p *Participant) IsFailed() bool {
	return p.Status == value_objects.ParticipantStatusFailed
}

// SetPublicKey sets the participant's public key
func (p *Participant) SetPublicKey(publicKey []byte) {
	p.PublicKey = publicKey
}

// UpdateActivity updates the last activity timestamp
func (p *Participant) UpdateActivity() {
	p.LastActivityAt = time.Now().UTC()
}

// IsTimedOut checks if the participant has exceeded the inactivity timeout
func (p *Participant) IsTimedOut(timeout time.Duration) bool {
	// If LastActivityAt is zero, use JoinedAt as reference
	refTime := p.LastActivityAt
	if refTime.IsZero() {
		refTime = p.JoinedAt
	}
	return time.Since(refTime) > timeout
}

// TimeSinceLastActivity returns the duration since the last activity
func (p *Participant) TimeSinceLastActivity() time.Duration {
	refTime := p.LastActivityAt
	if refTime.IsZero() {
		refTime = p.JoinedAt
	}
	return time.Since(refTime)
}
