package gateway

import (
	"context"
	"time"

	"github.com/google/uuid"
	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/service"
	gatewayuc "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/usecase/gateway"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/logger"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/jwt"
)

// LoginService handles user login operations
type LoginService interface {
	Login(ctx context.Context, input *LoginInput) (*LoginResponse, error)
}

// LoginInput represents the input for user login
type LoginInput struct {
	Email    string
	Password string
}

// LoginResponse represents the response after successful login
type LoginResponse struct {
	// User details
	UserID    string `json:"user_id"`
	UserEmail string `json:"user_email"`
	UserName  string `json:"user_name"`
	UserRole  string `json:"user_role"`

	// Tenant details
	TenantID string `json:"tenant_id"`

	// Session and tokens
	SessionID     string    `json:"session_id"`
	AccessToken   string    `json:"access_token"`
	AccessExpiry  time.Time `json:"access_expiry"`
	RefreshToken  string    `json:"refresh_token"`
	RefreshExpiry time.Time `json:"refresh_expiry"`

	LoginAt time.Time `json:"login_at"`
}

type loginService struct {
	loginUC        *gatewayuc.LoginUseCase
	sessionService service.SessionService
	jwtProvider    jwt.Provider
	logger         *zap.Logger
}

// NewLoginService creates a new login service
func NewLoginService(
	loginUC *gatewayuc.LoginUseCase,
	sessionService service.SessionService,
	jwtProvider jwt.Provider,
	logger *zap.Logger,
) LoginService {
	return &loginService{
		loginUC:        loginUC,
		sessionService: sessionService,
		jwtProvider:    jwtProvider,
		logger:         logger.Named("login-service"),
	}
}

// Login handles the complete login flow
func (s *loginService) Login(ctx context.Context, input *LoginInput) (*LoginResponse, error) {
	// CWE-532: Use hashed email to prevent PII in logs
	s.logger.Info("processing login request",
		logger.EmailHash(input.Email))

	// Execute login use case (validates credentials)
	loginOutput, err := s.loginUC.Execute(ctx, &gatewayuc.LoginInput{
		Email:    input.Email,
		Password: input.Password,
	})
	if err != nil {
		s.logger.Error("login failed", zap.Error(err))
		return nil, err
	}

	// CWE-532: Use hashed email to prevent PII in logs
	s.logger.Info("credentials validated successfully",
		zap.String("user_id", loginOutput.UserID),
		logger.EmailHash(loginOutput.UserEmail),
		zap.String("tenant_id", loginOutput.TenantID))

	// Parse tenant ID to UUID
	tenantUUID, err := uuid.Parse(loginOutput.TenantID)
	if err != nil {
		s.logger.Error("failed to parse tenant ID", zap.Error(err))
		return nil, err
	}

	// Parse user ID to UUID
	userUUID, err := uuid.Parse(loginOutput.UserID)
	if err != nil {
		s.logger.Error("failed to parse user ID", zap.Error(err))
		return nil, err
	}

	// CWE-384: Invalidate all existing sessions before creating new one (Session Fixation Prevention)
	// This ensures that any session IDs an attacker may have obtained are invalidated
	s.logger.Info("invalidating existing sessions for security",
		zap.String("user_uuid", userUUID.String()))
	if err := s.sessionService.InvalidateUserSessions(ctx, userUUID); err != nil {
		// Log warning but don't fail login - this is best effort cleanup
		s.logger.Warn("failed to invalidate existing sessions (non-fatal)",
			zap.String("user_uuid", userUUID.String()),
			zap.Error(err))
	}

	// Create new session in two-tier cache
	session, err := s.sessionService.CreateSession(
		ctx,
		0, // UserID as uint64 - not used in our UUID-based system
		userUUID,
		loginOutput.UserEmail,
		loginOutput.UserName,
		loginOutput.UserRole,
		tenantUUID,
	)
	if err != nil {
		s.logger.Error("failed to create session", zap.Error(err))
		return nil, err
	}

	s.logger.Info("session created", zap.String("session_id", session.ID))

	// Generate JWT access and refresh tokens
	accessToken, accessExpiry, refreshToken, refreshExpiry, err := s.jwtProvider.GenerateTokenPair(
		session.ID,
		AccessTokenDuration,
		RefreshTokenDuration,
	)
	if err != nil {
		s.logger.Error("failed to generate tokens", zap.Error(err))
		// Clean up session
		_ = s.sessionService.DeleteSession(ctx, session.ID)
		return nil, err
	}

	s.logger.Info("login completed successfully",
		zap.String("user_id", loginOutput.UserID),
		zap.String("tenant_id", loginOutput.TenantID),
		zap.String("session_id", session.ID))

	return &LoginResponse{
		UserID:        loginOutput.UserID,
		UserEmail:     loginOutput.UserEmail,
		UserName:      loginOutput.UserName,
		UserRole:      loginOutput.UserRole,
		TenantID:      loginOutput.TenantID,
		SessionID:     session.ID,
		AccessToken:   accessToken,
		AccessExpiry:  accessExpiry,
		RefreshToken:  refreshToken,
		RefreshExpiry: refreshExpiry,
		LoginAt:       time.Now().UTC(),
	}, nil
}
