package use_cases

import (
	"context"
	"time"

	"github.com/google/uuid"
	"github.com/rwadurian/mpc-system/pkg/logger"
	"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"
)

// RouteMessageInput contains input for routing a message
type RouteMessageInput struct {
	SessionID   uuid.UUID
	FromParty   string
	ToParties   []string // nil means broadcast
	RoundNumber int
	MessageType string
	Payload     []byte // Encrypted MPC message
}

// RouteMessageUseCase implements the route message use case
type RouteMessageUseCase struct {
	sessionRepo    repositories.SessionRepository
	messageRepo    repositories.MessageRepository
	messageBroker  output.MessageBrokerPort
	coordinatorSvc *services.SessionCoordinatorService
}

// NewRouteMessageUseCase creates a new route message use case
func NewRouteMessageUseCase(
	sessionRepo repositories.SessionRepository,
	messageRepo repositories.MessageRepository,
	messageBroker output.MessageBrokerPort,
) *RouteMessageUseCase {
	return &RouteMessageUseCase{
		sessionRepo:    sessionRepo,
		messageRepo:    messageRepo,
		messageBroker:  messageBroker,
		coordinatorSvc: services.NewSessionCoordinatorService(),
	}
}

// Execute executes the route message use case
func (uc *RouteMessageUseCase) Execute(
	ctx context.Context,
	input RouteMessageInput,
) error {
	// 1. Load session
	session, err := uc.sessionRepo.FindByUUID(ctx, input.SessionID)
	if err != nil {
		return err
	}

	// 2. Validate sender
	fromPartyID, err := value_objects.NewPartyID(input.FromParty)
	if err != nil {
		return err
	}

	// 3. Validate target parties
	toParties := make([]value_objects.PartyID, len(input.ToParties))
	for i, partyStr := range input.ToParties {
		partyID, err := value_objects.NewPartyID(partyStr)
		if err != nil {
			return err
		}
		toParties[i] = partyID
	}

	// 4. Validate message routing
	if err := uc.coordinatorSvc.ValidateMessageRouting(ctx, session, fromPartyID, toParties); err != nil {
		return err
	}

	// 4.1 Update sender's activity timestamp (they're actively participating)
	if err := session.UpdatePartyActivity(fromPartyID); err != nil {
		logger.Warn("failed to update sender activity",
			zap.String("session_id", session.ID.String()),
			zap.String("party_id", input.FromParty),
			zap.Error(err))
	}

	// 4.2 Save updated activity timestamp
	if err := uc.sessionRepo.Update(ctx, session); err != nil {
		logger.Warn("failed to persist activity update",
			zap.String("session_id", session.ID.String()),
			zap.Error(err))
	}

	// 5. Create message entity
	msg := entities.NewSessionMessage(
		session.ID,
		fromPartyID,
		toParties,
		input.RoundNumber,
		input.MessageType,
		input.Payload,
	)

	// 6. Persist message (for offline scenarios)
	if err := uc.messageRepo.SaveMessage(ctx, msg); err != nil {
		return err
	}

	// 7. Route message to target parties
	if len(toParties) == 0 {
		// Broadcast to all other participants
		for _, p := range session.Participants {
			if !p.PartyID.Equals(fromPartyID) {
				if err := uc.sendMessage(ctx, p.PartyID.String(), msg); err != nil {
					logger.Error("failed to send broadcast message",
						zap.String("session_id", session.ID.String()),
						zap.String("to_party", p.PartyID.String()),
						zap.Error(err))
				}
			}
		}
	} else {
		// Send to specific parties
		for _, toParty := range toParties {
			if err := uc.sendMessage(ctx, toParty.String(), msg); err != nil {
				logger.Error("failed to send unicast message",
					zap.String("session_id", session.ID.String()),
					zap.String("to_party", toParty.String()),
					zap.Error(err))
			}
		}
	}

	// 8. Publish message event
	event := output.MPCMessageEvent{
		MessageID:   msg.ID.String(),
		SessionID:   session.ID.String(),
		FromParty:   input.FromParty,
		ToParties:   input.ToParties,
		IsBroadcast: len(input.ToParties) == 0,
		RoundNumber: input.RoundNumber,
		CreatedAt:   time.Now().UnixMilli(),
	}
	if err := uc.messageBroker.PublishEvent(ctx, output.TopicMPCMessage, event); err != nil {
		logger.Error("failed to publish message event",
			zap.String("message_id", msg.ID.String()),
			zap.Error(err))
	}

	return nil
}

// sendMessage sends a message to a party via the message broker
func (uc *RouteMessageUseCase) sendMessage(ctx context.Context, partyID string, msg *entities.SessionMessage) error {
	messageDTO := msg.ToDTO()
	return uc.messageBroker.PublishMessage(ctx, partyID, messageDTO)
}

// GetMessagesInput contains input for getting messages
type GetMessagesInput struct {
	SessionID uuid.UUID
	PartyID   string
	AfterTime *time.Time
}

// GetMessagesUseCase retrieves messages for a party
type GetMessagesUseCase struct {
	sessionRepo repositories.SessionRepository
	messageRepo repositories.MessageRepository
}

// NewGetMessagesUseCase creates a new get messages use case
func NewGetMessagesUseCase(
	sessionRepo repositories.SessionRepository,
	messageRepo repositories.MessageRepository,
) *GetMessagesUseCase {
	return &GetMessagesUseCase{
		sessionRepo: sessionRepo,
		messageRepo: messageRepo,
	}
}

// Execute retrieves messages for a party
func (uc *GetMessagesUseCase) Execute(
	ctx context.Context,
	input GetMessagesInput,
) ([]*entities.SessionMessage, error) {
	// 1. Load session to validate
	session, err := uc.sessionRepo.FindByUUID(ctx, input.SessionID)
	if err != nil {
		return nil, err
	}

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

	// 3. Validate party is a participant
	if !session.IsParticipant(partyID) {
		return nil, services.ErrNotAParticipant
	}

	// 4. Get messages
	afterTime := time.Time{}
	if input.AfterTime != nil {
		afterTime = *input.AfterTime
	}

	messages, err := uc.messageRepo.GetMessages(ctx, session.ID, partyID, afterTime)
	if err != nil {
		return nil, err
	}

	return messages, nil
}
