package gateway

import (
	"context"
	"errors"

	"go.uber.org/zap"

	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/logger"
)

var (
	ErrInvalidCredentials = errors.New("invalid email or password")
)

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

// LoginOutput represents the output after successful login
type LoginOutput struct {
	UserID    string
	UserEmail string
	UserName  string
	UserRole  string
	TenantID  string
}

// LoginUseCase handles user authentication
// Orchestrates the login workflow by coordinating focused usecases
type LoginUseCase struct {
	// Focused usecases
	getUserByEmailUC *GetUserByEmailUseCase
	verifyPasswordUC *VerifyPasswordUseCase
	logger           *zap.Logger
}

// NewLoginUseCase creates a new login use case
func NewLoginUseCase(
	getUserByEmailUC *GetUserByEmailUseCase,
	verifyPasswordUC *VerifyPasswordUseCase,
	logger *zap.Logger,
) *LoginUseCase {
	return &LoginUseCase{
		getUserByEmailUC: getUserByEmailUC,
		verifyPasswordUC: verifyPasswordUC,
		logger:           logger.Named("login-usecase"),
	}
}

// ProvideLoginUseCase creates a new LoginUseCase for dependency injection
func ProvideLoginUseCase(
	getUserByEmailUC *GetUserByEmailUseCase,
	verifyPasswordUC *VerifyPasswordUseCase,
	logger *zap.Logger,
) *LoginUseCase {
	return NewLoginUseCase(getUserByEmailUC, verifyPasswordUC, logger)
}

// Execute orchestrates the login workflow using focused usecases
// CWE-208: Observable Timing Discrepancy - Uses timing-safe authentication
func (uc *LoginUseCase) Execute(ctx context.Context, input *LoginInput) (*LoginOutput, error) {
	// CWE-532: Use hashed email to prevent PII in logs
	uc.logger.Info("authenticating user",
		logger.EmailHash(input.Email))

	// Step 1: Get user by email globally (no tenant_id required for login)
	// Note: This returns ErrInvalidCredentials (not ErrUserNotFound) for security
	user, err := uc.getUserByEmailUC.Execute(ctx, input.Email)

	// CWE-208: TIMING ATTACK MITIGATION
	// We must ALWAYS verify the password, even if the user doesn't exist.
	// This prevents timing-based user enumeration attacks.
	//
	// Timing attack scenario without mitigation:
	// - If user exists: database lookup (~10ms) + Argon2 hashing (~100ms) = ~110ms
	// - If user doesn't exist: database lookup (~10ms) = ~10ms
	// Attacker can measure response time to enumerate valid email addresses.
	//
	// With mitigation:
	// - If user exists: database lookup + Argon2 hashing
	// - If user doesn't exist: database lookup + Argon2 dummy hashing
	// Both paths take approximately the same time (~110ms).

	var passwordHash string
	userExists := (err == nil && user != nil)

	if userExists {
		// User exists - use real password hash
		if user.SecurityData != nil {
			passwordHash = user.SecurityData.PasswordHash
		}
	}
	// If user doesn't exist, passwordHash remains empty string
	// The verifyPasswordUC will use dummy hash for timing safety

	// Step 2: Verify password - ALWAYS executed regardless of user existence
	if err := uc.verifyPasswordUC.ExecuteTimingSafe(input.Password, passwordHash, userExists); err != nil {
		// CWE-532: Use hashed email to prevent PII in logs
		if userExists {
			uc.logger.Warn("login failed: password verification failed",
				logger.EmailHash(input.Email),
				zap.String("tenant_id", user.TenantID))
		} else {
			uc.logger.Warn("login failed: user not found",
				logger.EmailHash(input.Email))
		}
		// Always return the same generic error regardless of reason
		return nil, ErrInvalidCredentials
	}

	// Now check if user lookup failed (after timing-safe password verification)
	if err != nil {
		// This should never happen because ExecuteTimingSafe should have failed
		// But keep for safety
		uc.logger.Error("unexpected error after password verification", zap.Error(err))
		return nil, ErrInvalidCredentials
	}

	// CWE-532: Use hashed email to prevent PII in logs
	uc.logger.Info("user authenticated successfully",
		zap.String("user_id", user.ID),
		logger.EmailHash(user.Email),
		zap.String("tenant_id", user.TenantID))

	// Convert role to string (1="executive", 2="manager", 3="staff")
	roleStr := getRoleString(user.Role)

	// Step 3: Build output
	return &LoginOutput{
		UserID:    user.ID,
		UserEmail: user.Email,
		UserName:  user.Name,
		UserRole:  roleStr,
		TenantID:  user.TenantID,
	}, nil
}

// getRoleString converts numeric role to string representation
func getRoleString(role int) string {
	switch role {
	case 1:
		return "executive"
	case 2:
		return "manager"
	case 3:
		return "staff"
	default:
		return "unknown"
	}
}
