package service

import (
	"errors"
	"log"
	"time"

	"aggios-app/backend/internal/config"
	"aggios-app/backend/internal/domain"
	"aggios-app/backend/internal/repository"

	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
	"golang.org/x/crypto/bcrypt"
)

var (
	ErrEmailAlreadyExists = errors.New("email already registered")
	ErrInvalidCredentials = errors.New("invalid email or password")
	ErrWeakPassword       = errors.New("password too weak")
	ErrSubdomainTaken     = errors.New("subdomain already taken")
	ErrUnauthorized       = errors.New("unauthorized access")
)

// AuthService handles authentication business logic
type AuthService struct {
	userRepo     *repository.UserRepository
	tenantRepo   *repository.TenantRepository
	crmRepo      *repository.CRMRepository
	cfg          *config.Config
}

// NewAuthService creates a new auth service
func NewAuthService(userRepo *repository.UserRepository, tenantRepo *repository.TenantRepository, crmRepo *repository.CRMRepository, cfg *config.Config) *AuthService {
	return &AuthService{
		userRepo:     userRepo,
		tenantRepo:   tenantRepo,
		crmRepo:      crmRepo,
		cfg:          cfg,
	}
}

// Register creates a new user account
func (s *AuthService) Register(req domain.CreateUserRequest) (*domain.User, error) {
	// Validate password strength
	if len(req.Password) < s.cfg.Security.PasswordMinLength {
		return nil, ErrWeakPassword
	}

	// Check if email already exists
	exists, err := s.userRepo.EmailExists(req.Email)
	if err != nil {
		return nil, err
	}
	if exists {
		return nil, ErrEmailAlreadyExists
	}

	// Hash password
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
	if err != nil {
		return nil, err
	}

	// Create user
	user := &domain.User{
		Email:    req.Email,
		Password: string(hashedPassword),
		Name:     req.Name,
	}

	if err := s.userRepo.Create(user); err != nil {
		return nil, err
	}

	return user, nil
}

// Login authenticates a user and returns a JWT token
func (s *AuthService) Login(req domain.LoginRequest) (*domain.LoginResponse, error) {
	// Find user by email
	user, err := s.userRepo.FindByEmail(req.Email)
	if err != nil {
		log.Printf("❌ DB error finding user %s: %v", req.Email, err)
		return nil, err
	}
	if user == nil {
		log.Printf("❌ User not found: %s", req.Email)
		return nil, ErrInvalidCredentials
	}

	log.Printf("🔍 Attempting login for %s with password_hash: %.10s...", req.Email, user.Password)
	log.Printf("🔍 Provided password length: %d", len(req.Password))

	// Verify password
	if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
		log.Printf("❌ Password mismatch for %s: %v", req.Email, err)
		return nil, ErrInvalidCredentials
	}

	// Generate JWT token
	token, err := s.generateToken(user)
	if err != nil {
		return nil, err
	}

	response := &domain.LoginResponse{
		Token: token,
		User:  *user,
	}

	// If user has a tenant, get the subdomain
	if user.TenantID != nil {
		tenant, err := s.tenantRepo.FindByID(*user.TenantID)
		if err == nil && tenant != nil {
			response.Subdomain = &tenant.Subdomain
		}
	}

	return response, nil
}

func (s *AuthService) generateToken(user *domain.User) (string, error) {
	claims := jwt.MapClaims{
		"user_id":   user.ID.String(),
		"email":     user.Email,
		"role":      user.Role,
		"tenant_id": nil,
		"exp":       time.Now().Add(time.Hour * 24 * 7).Unix(), // 7 days
	}
	
	if user.TenantID != nil {
		claims["tenant_id"] = user.TenantID.String()
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(s.cfg.JWT.Secret))
}

// ChangePassword changes a user's password
func (s *AuthService) ChangePassword(userID string, currentPassword, newPassword string) error {
	// Validate new password strength
	if len(newPassword) < s.cfg.Security.PasswordMinLength {
		return ErrWeakPassword
	}

	// Parse userID
	uid, err := parseUUID(userID)
	if err != nil {
		return ErrInvalidCredentials
	}

	// Find user
	user, err := s.userRepo.FindByID(uid)
	if err != nil {
		return err
	}
	if user == nil {
		return ErrInvalidCredentials
	}

	// Verify current password
	if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(currentPassword)); err != nil {
		return ErrInvalidCredentials
	}

	// Hash new password
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	// Update password
	return s.userRepo.UpdatePassword(userID, string(hashedPassword))
}

func parseUUID(s string) (uuid.UUID, error) {
	return uuid.Parse(s)
}

// GenerateCustomerToken gera um token JWT para um cliente do CRM
func (s *AuthService) GenerateCustomerToken(customerID, tenantID, email string) (string, error) {
	claims := jwt.MapClaims{
		"customer_id": customerID,
		"tenant_id":   tenantID,
		"email":       email,
		"type":        "customer_portal",
		"exp":         time.Now().Add(time.Hour * 24 * 30).Unix(), // 30 dias
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(s.cfg.JWT.Secret))
}

// UnifiedLogin autentica qualquer tipo de usuário (agência ou cliente) e retorna token unificado
func (s *AuthService) UnifiedLogin(req domain.UnifiedLoginRequest) (*domain.UnifiedLoginResponse, error) {
	email := req.Email
	password := req.Password

	// TENTATIVA 1: Buscar em users (agência)
	user, err := s.userRepo.FindByEmail(email)
	if err == nil && user != nil {
		// Verificar senha
		if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
			log.Printf("❌ Password mismatch for agency user %s", email)
			return nil, ErrInvalidCredentials
		}

		// SUPERADMIN usa login próprio em outro domínio, não deve usar esta rota
		if user.Role == "SUPERADMIN" {
			log.Printf("🚫 SUPERADMIN attempted unified login - redirecting to proper endpoint")
			return nil, errors.New("superadmins devem usar o painel administrativo")
		}

		// Gerar token unificado para agency_user
		token, err := s.generateUnifiedToken(user.ID.String(), domain.UserTypeAgency, email, user.Role, user.AgencyRole, user.TenantID)
		if err != nil {
			log.Printf("❌ Error generating unified token: %v", err)
			return nil, err
		}

		// Buscar subdomain se tiver tenant
		subdomain := ""
		tenantID := ""
		if user.TenantID != nil {
			tenantID = user.TenantID.String()
			tenant, err := s.tenantRepo.FindByID(*user.TenantID)
			if err == nil && tenant != nil {
				subdomain = tenant.Subdomain
			}
		}

		log.Printf("✅ Agency user logged in: %s (type=agency_user, role=%s, agency_role=%s)", email, user.Role, user.AgencyRole)

		return &domain.UnifiedLoginResponse{
			Token:      token,
			UserType:   domain.UserTypeAgency,
			UserID:     user.ID.String(),
			Email:      email,
			Name:       user.Name,
			Role:       user.Role,
			AgencyRole: user.AgencyRole,
			TenantID:   tenantID,
			Subdomain:  subdomain,
		}, nil
	}

	// TENTATIVA 2: Buscar em crm_customers
	log.Printf("🔍 Attempting to find customer in CRM: %s", email)
	customer, err := s.crmRepo.GetCustomerByEmail(email)
	log.Printf("🔍 CRM GetCustomerByEmail result: customer=%v, err=%v", customer != nil, err)
	if err == nil && customer != nil {
		// Verificar se tem acesso ao portal
		if !customer.HasPortalAccess {
			log.Printf("🚫 Customer %s has no portal access", email)
			return nil, errors.New("acesso ao portal não autorizado. Entre em contato com o administrador")
		}

		// Verificar senha
		if customer.PasswordHash == "" {
			log.Printf("❌ Customer %s has no password set", email)
			return nil, ErrInvalidCredentials
		}

		if err := bcrypt.CompareHashAndPassword([]byte(customer.PasswordHash), []byte(password)); err != nil {
			log.Printf("❌ Password mismatch for customer %s", email)
			return nil, ErrInvalidCredentials
		}

		// Atualizar último login
		if err := s.crmRepo.UpdateCustomerLastLogin(customer.ID); err != nil {
			log.Printf("⚠️ Warning: Failed to update last login for customer %s: %v", customer.ID, err)
		}

		// Gerar token unificado
		tenantUUID, _ := uuid.Parse(customer.TenantID)
		token, err := s.generateUnifiedToken(customer.ID, domain.UserTypeCustomer, email, "", "", &tenantUUID)
		if err != nil {
			log.Printf("❌ Error generating unified token: %v", err)
			return nil, err
		}

		// Buscar subdomain do tenant
		subdomain := ""
		if tenantUUID != uuid.Nil {
			tenant, err := s.tenantRepo.FindByID(tenantUUID)
			if err == nil && tenant != nil {
				subdomain = tenant.Subdomain
			}
		}

		log.Printf("✅ Customer logged in: %s (tenant=%s)", email, customer.TenantID)

		return &domain.UnifiedLoginResponse{
			Token:     token,
			UserType:  domain.UserTypeCustomer,
			UserID:    customer.ID,
			Email:     email,
			Name:      customer.Name,
			TenantID:  customer.TenantID,
			Subdomain: subdomain,
		}, nil
	}

	// Não encontrou em nenhuma tabela
	log.Printf("❌ User not found: %s", email)
	return nil, ErrInvalidCredentials
}

// generateUnifiedToken cria um JWT com claims unificadas
func (s *AuthService) generateUnifiedToken(userID string, userType domain.UserType, email, role, agencyRole string, tenantID *uuid.UUID) (string, error) {
	tenantIDStr := ""
	if tenantID != nil {
		tenantIDStr = tenantID.String()
	}

	claims := domain.UnifiedClaims{
		UserID:     userID,
		UserType:   userType,
		TenantID:   tenantIDStr,
		Email:      email,
		Role:       role,
		AgencyRole: agencyRole,
		RegisteredClaims: jwt.RegisteredClaims{
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * 30)), // 30 dias
			IssuedAt:  jwt.NewNumericDate(time.Now()),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(s.cfg.JWT.Secret))
}


