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/repositories"
	"go.uber.org/zap"
)

// CloseSessionUseCase implements the close session use case
type CloseSessionUseCase struct {
	sessionRepo    repositories.SessionRepository
	messageRepo    repositories.MessageRepository
	eventPublisher output.MessageBrokerPort
}

// NewCloseSessionUseCase creates a new close session use case
func NewCloseSessionUseCase(
	sessionRepo repositories.SessionRepository,
	messageRepo repositories.MessageRepository,
	eventPublisher output.MessageBrokerPort,
) *CloseSessionUseCase {
	return &CloseSessionUseCase{
		sessionRepo:    sessionRepo,
		messageRepo:    messageRepo,
		eventPublisher: eventPublisher,
	}
}

// Execute executes the close session use case
func (uc *CloseSessionUseCase) Execute(
	ctx context.Context,
	sessionID uuid.UUID,
) error {
	// 1. Load session
	session, err := uc.sessionRepo.FindByUUID(ctx, sessionID)
	if err != nil {
		return err
	}

	// 2. Mark session as failed if not already completed
	if session.Status.IsActive() {
		if err := session.Fail(); err != nil {
			return err
		}

		// Publish session failed event
		event := output.SessionFailedEvent{
			SessionID: session.ID.String(),
			Reason:    "session closed by user",
			FailedAt:  time.Now().UnixMilli(),
		}
		if err := uc.eventPublisher.PublishEvent(ctx, output.TopicSessionFailed, event); err != nil {
			logger.Error("failed to publish session failed event",
				zap.String("session_id", session.ID.String()),
				zap.Error(err))
		}
	}

	// 3. Save updated session
	if err := uc.sessionRepo.Update(ctx, session); err != nil {
		return err
	}

	// 4. Clean up messages for this session
	if err := uc.messageRepo.DeleteBySession(ctx, session.ID); err != nil {
		logger.Error("failed to delete session messages",
			zap.String("session_id", session.ID.String()),
			zap.Error(err))
		// Don't fail the operation for message cleanup errors
	}

	return nil
}

// ExpireSessionsUseCase handles expiring stale sessions
type ExpireSessionsUseCase struct {
	sessionRepo    repositories.SessionRepository
	eventPublisher output.MessageBrokerPort
}

// NewExpireSessionsUseCase creates a new expire sessions use case
func NewExpireSessionsUseCase(
	sessionRepo repositories.SessionRepository,
	eventPublisher output.MessageBrokerPort,
) *ExpireSessionsUseCase {
	return &ExpireSessionsUseCase{
		sessionRepo:    sessionRepo,
		eventPublisher: eventPublisher,
	}
}

// Execute finds and expires all stale sessions
func (uc *ExpireSessionsUseCase) Execute(ctx context.Context) (int, error) {
	// 1. Find expired sessions
	sessions, err := uc.sessionRepo.FindExpired(ctx)
	if err != nil {
		return 0, err
	}

	expiredCount := 0
	for _, session := range sessions {
		// 2. Mark session as expired
		if err := session.Expire(); err != nil {
			logger.Error("failed to expire session",
				zap.String("session_id", session.ID.String()),
				zap.Error(err))
			continue
		}

		// 3. Save updated session
		if err := uc.sessionRepo.Update(ctx, session); err != nil {
			logger.Error("failed to update expired session",
				zap.String("session_id", session.ID.String()),
				zap.Error(err))
			continue
		}

		// 4. Publish session expired event
		event := output.SessionExpiredEvent{
			SessionID: session.ID.String(),
			ExpiredAt: time.Now().UnixMilli(),
		}
		if err := uc.eventPublisher.PublishEvent(ctx, output.TopicSessionExpired, event); err != nil {
			logger.Error("failed to publish session expired event",
				zap.String("session_id", session.ID.String()),
				zap.Error(err))
		}

		expiredCount++
	}

	return expiredCount, nil
}
