package services

import (
	"context"
	"errors"
	"strings"
	"time"

	"backend/internal/apperrors"
	"backend/internal/models"
	"backend/internal/repository"

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

type AuthService struct {
	clientUserRepo *repository.ClientUserRepository
	jwtSecret      []byte
	jwtExpiry      int // hours
}

func NewAuthService(clientUserRepo *repository.ClientUserRepository, jwtSecret string, jwtExpiry int) *AuthService {
	return &AuthService{
		clientUserRepo: clientUserRepo,
		jwtSecret:      []byte(jwtSecret),
		jwtExpiry:      jwtExpiry,
	}
}

func (s *AuthService) Login(ctx context.Context, usernameOrEmail, password string) (*models.LoginResponse, error) {
	// Try to find user by username first, then by email
	var user *models.ClientUser
	var err error

	// Check if input looks like an email (contains @)
	if strings.Contains(usernameOrEmail, "@") {
		user, err = s.clientUserRepo.FindByEmail(ctx, usernameOrEmail)
	} else {
		user, err = s.clientUserRepo.FindByUsername(ctx, usernameOrEmail)
	}

	if err != nil {
		// Check if it's a "not found" error
		if err.Error() == "client user not found" {
			return nil, apperrors.ErrAccountNotFound
		}
		// For other database errors, return generic error
		return nil, apperrors.NewAuthError("database_error", "An error occurred while processing your request", "AUTH_DB_ERROR")
	}

	// Check if account is locked
	if user.LockedUntil != nil && user.LockedUntil.After(time.Now()) {
		return nil, apperrors.ErrAccountLocked
	}

	// Check if user is active
	if !user.IsActive {
		return nil, apperrors.ErrAccountInactive
	}

	// Check account status
	if user.Status != "active" {
		if user.Status == "suspended" {
			return nil, apperrors.ErrAccountSuspended
		}
		return nil, apperrors.NewAuthError("account_inactive", "Your account is not active. Please contact support.", "ACCOUNT_INACTIVE")
	}

	// Verify password - this is the critical check that should be separate
	if user.Username == nil {
		return nil, apperrors.ErrAccountNotFound
	}

	if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
		// Password is incorrect - increment failed login attempts
		_ = s.clientUserRepo.IncrementFailedLoginAttempts(ctx, user.ID)
		return nil, apperrors.ErrIncorrectPassword
	}

	// Generate JWT token with both client_user_id and client_id
	token, err := s.generateToken(user.ID.String(), user.ClientID, *user.Username)
	if err != nil {
		return nil, apperrors.ErrTokenGenerationFailed
	}

	// Get client IP from request context if available
	ipAddress := ""
	if ctx.Value("ip_address") != nil {
		ipAddress = ctx.Value("ip_address").(string)
	}

	// Update last login
	_ = s.clientUserRepo.UpdateLastLogin(ctx, user.ID, ipAddress)

	// Build user info response (excluding id, client_id, is_active, last_login, created_at)
	userInfo := models.LoginUserInfo{
		FirstName: user.FirstName,
		LastName:  user.LastName,
		Email:     user.Email,
		Phone:     user.Phone,
		Username:  user.Username,
		Role:      user.Role,
		Status:    user.Status,
		AvatarURL: user.AvatarURL,
	}

	// Return response
	return &models.LoginResponse{
		Message: "Login successful",
		Token:   token,
		User:    userInfo,
	}, nil
}

func (s *AuthService) ValidateToken(tokenString string) (*jwt.Token, error) {
	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, errors.New("unexpected signing method")
		}
		return s.jwtSecret, nil
	})

	if err != nil {
		return nil, err
	}

	if !token.Valid {
		return nil, errors.New("invalid token")
	}

	return token, nil
}

func (s *AuthService) GetClientIDFromToken(token *jwt.Token) (string, error) {
	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return "", errors.New("invalid token claims")
	}

	clientID, ok := claims["client_id"].(string)
	if !ok {
		return "", errors.New("client_id not found in token")
	}

	return clientID, nil
}

func (s *AuthService) GetClientUserIDFromToken(token *jwt.Token) (string, error) {
	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return "", errors.New("invalid token claims")
	}

	clientUserID, ok := claims["client_user_id"].(string)
	if !ok {
		return "", errors.New("client_user_id not found in token")
	}

	return clientUserID, nil
}

func (s *AuthService) generateToken(clientUserID, clientID, username string) (string, error) {
	expiryHours := s.jwtExpiry
	if expiryHours <= 0 {
		expiryHours = 24 // default to 24 hours
	}

	claims := jwt.MapClaims{
		"client_user_id": clientUserID,
		"client_id":      clientID,
		"username":       username,
		"exp":            time.Now().Add(time.Hour * time.Duration(expiryHours)).Unix(),
		"iat":            time.Now().Unix(),
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(s.jwtSecret)
}
