package gateway

import (
	"context"
	"fmt"
	"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"
	tenantuc "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/usecase/tenant"
	userusecase "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/usecase/user"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/distributedmutex"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/logger"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/jwt"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/transaction"
)

const (
	// Role constants for the three-tier role system (numeric values)
	RoleExecutive int = 1 // Can access ANY tenant ANYTIME (root/SaaS owner)
	RoleManager   int = 2 // User who registered and created tenant (can create users)
	RoleStaff     int = 3 // User created by manager (cannot create users/tenants)

	// Role names for display/API responses
	RoleExecutiveName = "executive"
	RoleManagerName   = "manager"
	RoleStaffName     = "staff"

	// AccessTokenDuration is the lifetime of an access token
	AccessTokenDuration = 15 * time.Minute
	// RefreshTokenDuration is the lifetime of a refresh token
	RefreshTokenDuration = 7 * 24 * time.Hour // 7 days
)

// RegisterService handles user registration operations
type RegisterService interface {
	Register(ctx context.Context, input *RegisterInput) (*RegisterResponse, error)
}

// RegisterInput represents the input for user registration
// This is an alias to the usecase layer type for backward compatibility
type RegisterInput = gatewayuc.RegisterInput

// RegisterResponse represents the response after successful registration
type RegisterResponse 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"`
	TenantName string `json:"tenant_name"`
	TenantSlug string `json:"tenant_slug"`

	// 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"`

	CreatedAt time.Time `json:"created_at"`
}

type registerService struct {
	// Focused usecases for validation and creation
	validateInputUC       *gatewayuc.ValidateRegistrationInputUseCase
	checkTenantSlugUC     *gatewayuc.CheckTenantSlugAvailabilityUseCase
	checkPasswordBreachUC *gatewayuc.CheckPasswordBreachUseCase // CWE-521: Password breach checking
	hashPasswordUC        *gatewayuc.HashPasswordUseCase

	// Tenant creation - focused usecases following Clean Architecture
	validateTenantSlugUC *tenantuc.ValidateTenantSlugUniqueUseCase
	createTenantEntityUC *tenantuc.CreateTenantEntityUseCase
	saveTenantToRepoUC   *tenantuc.SaveTenantToRepoUseCase

	// User creation - focused usecases following Clean Architecture
	validateUserEmailUC *userusecase.ValidateUserEmailUniqueUseCase
	createUserEntityUC  *userusecase.CreateUserEntityUseCase
	saveUserToRepoUC    *userusecase.SaveUserToRepoUseCase

	// Deletion usecases for compensation (SAGA pattern)
	deleteTenantUC *tenantuc.DeleteTenantUseCase
	deleteUserUC   *userusecase.DeleteUserUseCase

	// Distributed mutex for preventing race conditions (CWE-664)
	distributedMutex distributedmutex.Adapter

	// Session and token management
	sessionService service.SessionService
	jwtProvider    jwt.Provider

	logger *zap.Logger
}

// NewRegisterService creates a new register service
func NewRegisterService(
	validateInputUC *gatewayuc.ValidateRegistrationInputUseCase,
	checkTenantSlugUC *gatewayuc.CheckTenantSlugAvailabilityUseCase,
	checkPasswordBreachUC *gatewayuc.CheckPasswordBreachUseCase,
	hashPasswordUC *gatewayuc.HashPasswordUseCase,
	validateTenantSlugUC *tenantuc.ValidateTenantSlugUniqueUseCase,
	createTenantEntityUC *tenantuc.CreateTenantEntityUseCase,
	saveTenantToRepoUC *tenantuc.SaveTenantToRepoUseCase,
	validateUserEmailUC *userusecase.ValidateUserEmailUniqueUseCase,
	createUserEntityUC *userusecase.CreateUserEntityUseCase,
	saveUserToRepoUC *userusecase.SaveUserToRepoUseCase,
	deleteTenantUC *tenantuc.DeleteTenantUseCase,
	deleteUserUC *userusecase.DeleteUserUseCase,
	distributedMutex distributedmutex.Adapter,
	sessionService service.SessionService,
	jwtProvider jwt.Provider,
	logger *zap.Logger,
) RegisterService {
	return &registerService{
		validateInputUC:       validateInputUC,
		checkTenantSlugUC:     checkTenantSlugUC,
		checkPasswordBreachUC: checkPasswordBreachUC,
		hashPasswordUC:        hashPasswordUC,
		validateTenantSlugUC:  validateTenantSlugUC,
		createTenantEntityUC:  createTenantEntityUC,
		saveTenantToRepoUC:    saveTenantToRepoUC,
		validateUserEmailUC:   validateUserEmailUC,
		createUserEntityUC:    createUserEntityUC,
		saveUserToRepoUC:      saveUserToRepoUC,
		deleteTenantUC:        deleteTenantUC,
		deleteUserUC:          deleteUserUC,
		distributedMutex:      distributedMutex,
		sessionService:        sessionService,
		jwtProvider:           jwtProvider,
		logger:                logger.Named("register-service"),
	}
}

// Register handles the complete registration flow with SAGA pattern
// Orchestrates: validation → tenant creation → user creation → session → tokens
// Uses SAGA for automatic rollback if any database operation fails
func (s *registerService) Register(ctx context.Context, input *RegisterInput) (*RegisterResponse, error) {
	// CWE-532: Log with redacted sensitive information
	s.logger.Info("registering new user",
		logger.EmailHash(input.Email),
		logger.TenantSlugHash(input.TenantSlug))

	// Create SAGA for this registration workflow
	saga := transaction.NewSaga("user-registration", s.logger)

	// Step 1: Validate input (no DB writes, no compensation needed)
	validateInput := &gatewayuc.RegisterInput{
		Email:      input.Email,
		Password:   input.Password,
		FirstName:  input.FirstName,
		LastName:   input.LastName,
		TenantName: input.TenantName,
		TenantSlug: input.TenantSlug,
		Timezone:   input.Timezone,

		// Consent fields
		AgreeTermsOfService: input.AgreeTermsOfService,
		AgreePromotions:     input.AgreePromotions,
		AgreeToTrackingAcrossThirdPartyAppsAndServices: input.AgreeToTrackingAcrossThirdPartyAppsAndServices,

		// IP address for audit trail
		CreatedFromIPAddress: input.CreatedFromIPAddress,
	}
	if err := s.validateInputUC.Execute(validateInput); err != nil {
		s.logger.Error("input validation failed", zap.Error(err))
		return nil, err
	}

	// Step 2: Acquire distributed lock on tenant slug to prevent race conditions (CWE-664, CWE-755)
	// This prevents multiple concurrent registrations from creating duplicate tenants
	// with the same slug during the window between slug check and tenant creation
	lockKey := fmt.Sprintf("registration:tenant-slug:%s", input.TenantSlug)
	s.logger.Debug("acquiring distributed lock for tenant slug",
		zap.String("lock_key", lockKey))

	// CWE-755: Proper error handling - fail registration if lock cannot be obtained
	if err := s.distributedMutex.Acquire(ctx, lockKey); err != nil {
		s.logger.Error("failed to acquire registration lock",
			zap.Error(err),
			zap.String("tenant_slug", input.TenantSlug),
			zap.String("lock_key", lockKey))
		return nil, fmt.Errorf("registration temporarily unavailable, please try again later: %w", err)
	}
	defer func() {
		// Always release the lock when we're done, even if registration fails
		s.logger.Debug("releasing distributed lock for tenant slug",
			zap.String("lock_key", lockKey))
		if err := s.distributedMutex.Release(ctx, lockKey); err != nil {
			// Log error but don't fail registration if already completed
			s.logger.Error("failed to release lock after registration",
				zap.Error(err),
				zap.String("lock_key", lockKey))
		}
	}()

	s.logger.Debug("distributed lock acquired successfully",
		zap.String("lock_key", lockKey))

	// Step 3: Check if tenant slug is available (now protected by lock)
	// Even if another request checked at the same time, only one can proceed
	if err := s.checkTenantSlugUC.Execute(ctx, input.TenantSlug); err != nil {
		s.logger.Error("tenant slug check failed", zap.Error(err))
		return nil, err
	}

	// Step 4: Check if password has been breached (CWE-521: Password Breach Checking)
	// This prevents users from using passwords found in known data breaches
	if err := s.checkPasswordBreachUC.Execute(ctx, input.Password); err != nil {
		s.logger.Error("password breach check failed", zap.Error(err))
		return nil, err
	}

	// Step 5: Validate and hash password (no DB writes, no compensation needed)
	passwordHash, err := s.hashPasswordUC.Execute(input.Password)
	if err != nil {
		s.logger.Error("password hashing failed", zap.Error(err))
		return nil, err
	}

	// Step 6: Create tenant (FIRST DB WRITE - compensation required from here on)
	// Using focused use cases following Clean Architecture pattern

	// Step 6a: Validate tenant slug uniqueness
	if err := s.validateTenantSlugUC.Execute(ctx, input.TenantSlug); err != nil {
		s.logger.Error("tenant slug validation failed", zap.Error(err))
		return nil, err
	}

	// Step 6b: Create tenant entity with IP address
	tenant, err := s.createTenantEntityUC.Execute(&tenantuc.CreateTenantInput{
		Name:                 input.TenantName,
		Slug:                 input.TenantSlug,
		CreatedFromIPAddress: input.CreatedFromIPAddress,
	})
	if err != nil {
		s.logger.Error("tenant entity creation failed", zap.Error(err))
		return nil, err
	}

	// Step 6c: Save tenant to repository
	if err := s.saveTenantToRepoUC.Execute(ctx, tenant); err != nil {
		s.logger.Error("failed to save tenant", zap.Error(err))
		return nil, err
	}

	s.logger.Info("tenant created successfully",
		zap.String("tenant_id", tenant.ID),
		zap.String("tenant_slug", tenant.Slug))

	// Register compensation: if user creation fails, delete this tenant
	saga.AddCompensation(func(ctx context.Context) error {
		s.logger.Warn("compensating: deleting tenant due to user creation failure",
			zap.String("tenant_id", tenant.ID))
		return s.deleteTenantUC.Execute(ctx, tenant.ID)
	})

	// Step 7: Create user with hashed password (SECOND DB WRITE)
	// Using focused use cases following Clean Architecture pattern

	// Step 7a: Validate email uniqueness
	if err := s.validateUserEmailUC.Execute(ctx, tenant.ID, input.Email); err != nil {
		s.logger.Error("user email validation failed - executing compensating transactions",
			zap.String("tenant_id", tenant.ID),
			zap.Error(err))
		saga.Rollback(ctx)
		return nil, err
	}

	// Step 7b: Create user entity
	user, err := s.createUserEntityUC.Execute(tenant.ID, &userusecase.CreateUserInput{
		Email:                 input.Email,
		FirstName:             input.FirstName,
		LastName:              input.LastName,
		PasswordHash:          passwordHash,
		PasswordHashAlgorithm: "argon2id", // Set the algorithm used
		Role:                  RoleManager,
		Timezone:              input.Timezone,

		// Consent fields
		AgreeTermsOfService: input.AgreeTermsOfService,
		AgreePromotions:     input.AgreePromotions,
		AgreeToTrackingAcrossThirdPartyAppsAndServices: input.AgreeToTrackingAcrossThirdPartyAppsAndServices,

		// IP address for audit trail
		CreatedFromIPAddress: input.CreatedFromIPAddress,
	})
	if err != nil {
		s.logger.Error("user entity creation failed - executing compensating transactions",
			zap.String("tenant_id", tenant.ID),
			zap.Error(err))
		saga.Rollback(ctx)
		return nil, err
	}

	// Step 7c: Save user to repository
	if err := s.saveUserToRepoUC.Execute(ctx, tenant.ID, user); err != nil {
		s.logger.Error("failed to save user - executing compensating transactions",
			zap.String("tenant_id", tenant.ID),
			zap.String("user_id", user.ID),
			zap.Error(err))
		saga.Rollback(ctx)
		return nil, err
	}

	s.logger.Info("user created successfully",
		zap.String("user_id", user.ID),
		zap.String("tenant_id", tenant.ID))

	// Step 8: Parse UUIDs for session creation
	tenantUUID, err := uuid.Parse(tenant.ID)
	if err != nil {
		s.logger.Error("failed to parse tenant ID", zap.Error(err))
		// Rollback tenant and user
		saga.Rollback(ctx)
		return nil, err
	}

	userUUID, err := uuid.Parse(user.ID)
	if err != nil {
		s.logger.Error("failed to parse user ID", zap.Error(err))
		// Rollback tenant and user
		saga.Rollback(ctx)
		return nil, err
	}

	// Step 9: Create session in two-tier cache
	// Note: Session.UserID expects uint64, but we're using UUIDs
	// We'll use 0 for now and rely on UserUUID
	session, err := s.sessionService.CreateSession(
		ctx,
		0, // UserID as uint64 - not used in our UUID-based system
		userUUID,
		user.Email,
		user.FullName(),
		RoleManagerName, // Pass string name for session
		tenantUUID,
	)
	if err != nil {
		s.logger.Error("failed to create session", zap.Error(err))
		// Rollback tenant and user
		saga.Rollback(ctx)
		return nil, err
	}

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

	// Step 10: 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)
		// Rollback tenant and user
		saga.Rollback(ctx)
		return nil, err
	}

	// Success! Registration completed, distributed lock will be released by defer
	s.logger.Info("registration completed successfully",
		zap.String("user_id", user.ID),
		zap.String("tenant_id", tenant.ID),
		zap.String("session_id", session.ID))

	return &RegisterResponse{
		UserID:        user.ID,
		UserEmail:     user.Email,
		UserName:      user.FullName(),
		UserRole:      RoleManagerName, // Return string name for API response
		TenantID:      tenant.ID,
		TenantName:    tenant.Name,
		TenantSlug:    tenant.Slug,
		SessionID:     session.ID,
		AccessToken:   accessToken,
		AccessExpiry:  accessExpiry,
		RefreshToken:  refreshToken,
		RefreshExpiry: refreshExpiry,
		CreatedAt:     user.CreatedAt,
	}, nil
}
