package auth

import (
	"api_yal/internal/logger"
	"api_yal/internal/models"
	"api_yal/internal/repository"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"fmt"
	"time"

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

// AuthService интерфейс сервиса аутентификации
type AuthService interface {
	Register(req RegisterRequest) (*AuthResponse, error)
	Login(req LoginRequest) (*AuthResponse, string, error)
	RefreshToken(refreshToken string) (*RefreshTokenResponse, error)
	Logout(userID uint) error
	ValidateAccessToken(tokenString string) (*jwt.MapClaims, error)
	GetUserFromToken(claims *jwt.MapClaims) (*models.Account, error)
	
	// Reset password methods
	RequestPasswordReset(email string) (string, error) // Возвращает reset token
	ConfirmPasswordReset(token, newPassword string) error
	GenerateRefreshTokenForUser(userID uint) (string, error)
}

// authServiceImpl реализация сервиса аутентификации
type authServiceImpl struct {
	accountRepo       repository.AccountRepository
	jwtSecret         []byte
	accessTokenTTL    time.Duration
	refreshTokenTTL   time.Duration
	resetTokenTTL     time.Duration
}

// AuthServiceConfig конфигурация для сервиса аутентификации
type AuthServiceConfig struct {
	JWTSecret       string
	AccessTokenTTL  time.Duration // Рекомендуется 15-30 минут
	RefreshTokenTTL time.Duration // Рекомендуется 7-30 дней
	ResetTokenTTL   time.Duration // Рекомендуется 1 час
}

// NewAuthService создает новый экземпляр сервиса аутентификации
func NewAuthService(accountRepo repository.AccountRepository, config AuthServiceConfig) AuthService {
	return &authServiceImpl{
		accountRepo:       accountRepo,
		jwtSecret:         []byte(config.JWTSecret),
		accessTokenTTL:    config.AccessTokenTTL,
		refreshTokenTTL:   config.RefreshTokenTTL,
		resetTokenTTL:     config.ResetTokenTTL,
	}
}

// Register регистрирует нового пользователя
func (s *authServiceImpl) Register(req RegisterRequest) (*AuthResponse, error) {
	l := logger.Get()
	l.Info("Начало регистрации нового пользователя", zap.String("email", req.Email))

	// Проверяем, существует ли пользователь с таким email
	existingUser, err := s.accountRepo.GetByEmail(req.Email)
	if err == nil && existingUser != nil {
		return nil, ErrUserAlreadyExists
	}

	// Хешируем пароль
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
	if err != nil {
		l.Error("Ошибка хеширования пароля", zap.Error(err))
		return nil, err
	}

	// Формируем полное имя
	fullName := req.FirstName + " " + req.LastName

	// Создаем аккаунт
	newAcc := &models.Account{
		Email:        req.Email,
		PasswordHash: string(hashedPassword),
		FirstName:    req.FirstName,
		LastName:     req.LastName,
		FullName:     fullName,
		IsActive:     true,
		IsVerified:   false,
		Role:         "user",
	}

	// Сохраняем в базу данных
	if err := s.accountRepo.Create(newAcc); err != nil {
		l.Error("Ошибка создания аккаунта", zap.Error(err))
		return nil, err
	}

	// Генерируем access token
	accessToken, expiresAt, err := s.generateAccessToken(newAcc)
	if err != nil {
		return nil, err
	}

	// Формируем ответ
	response := &AuthResponse{
		Token:     accessToken,
		ExpiresAt: expiresAt,
		User: UserInfo{
			ID:        newAcc.Base.ID,
			Email:     newAcc.Email,
			FirstName: newAcc.FirstName,
			LastName:  newAcc.LastName,
			FullName:  newAcc.FullName,
			Role:      newAcc.Role,
		},
	}

	l.Info("Пользователь успешно зарегистрирован", zap.String("email", req.Email))
	return response, nil
}

// Login аутентифицирует пользователя
func (s *authServiceImpl) Login(req LoginRequest) (*AuthResponse, string, error) {
	l := logger.Get()
	l.Info("Начало входа пользователя", zap.String("email", req.Email))

	// Ищем пользователя по email
	account, err := s.accountRepo.GetByEmail(req.Email)
	if err != nil {
		l.Error("Пользователь не найден", zap.String("email", req.Email), zap.Error(err))
		return nil, "", ErrUserNotFound
	}

	// Проверяем, активен ли аккаунт
	if !account.IsActive {
		l.Error("Аккаунт деактивирован", zap.String("email", req.Email))
		return nil, "", errors.New("account is deactivated")
	}

	// Сравниваем пароли
	if err := bcrypt.CompareHashAndPassword([]byte(account.PasswordHash), []byte(req.Password)); err != nil {
		l.Error("Неверный пароль для пользователя", zap.String("email", req.Email))
		return nil, "", ErrInvalidPassword
	}

	// Генерируем токены
	accessToken, expiresAt, err := s.generateAccessToken(account)
	if err != nil {
		return nil, "", err
	}

	refreshToken, err := s.generateRefreshToken(account)
	if err != nil {
		return nil, "", err
	}

	// Формируем ответ
	response := &AuthResponse{
		Token:     accessToken,
		ExpiresAt: expiresAt,
		User: UserInfo{
			ID:        account.Base.ID,
			Email:     account.Email,
			FirstName: account.FirstName,
			LastName:  account.LastName,
			FullName:  account.FullName,
			Role:      account.Role,
		},
	}

	l.Info("Пользователь успешно вошел", zap.String("email", req.Email))
	return response, refreshToken, nil
}

// RequestPasswordReset запрашивает сброс пароля
func (s *authServiceImpl) RequestPasswordReset(email string) (string, error) {
	l := logger.Get()
	l.Info("Запрос сброса пароля", zap.String("email", email))

	// Проверяем существование пользователя
	account, err := s.accountRepo.GetByEmail(email)
	if err != nil {
		l.Error("Пользователь не найден", zap.String("email", email), zap.Error(err))
		return "", ErrUserNotFound
	}

	// Генерируем reset token
	resetToken, err := s.generateResetToken()
	if err != nil {
		l.Error("Ошибка генерации reset token", zap.Error(err))
		return "", err
	}

	// Сохраняем reset token в базу данных
	// Для этого нужно создать модель PasswordReset
	passwordReset := &models.PasswordReset{
		AccountID: account.Base.ID,
		Token:     resetToken,
		ExpiresAt: time.Now().Add(s.resetTokenTTL),
		Used:      false,
	}

	if err := s.accountRepo.CreatePasswordReset(passwordReset); err != nil {
		l.Error("Ошибка сохранения reset token", zap.Error(err))
		return "", err
	}

	l.Info("Reset token создан", zap.String("email", email), zap.String("token", resetToken))
	return resetToken, nil
}

// ConfirmPasswordReset подтверждает сброс пароля
func (s *authServiceImpl) ConfirmPasswordReset(token, newPassword string) error {
	l := logger.Get()
	l.Info("Подтверждение сброса пароля")

	// Находим reset token в базе
	passwordReset, err := s.accountRepo.GetPasswordResetByToken(token)
	if err != nil {
		l.Error("Reset token не найден", zap.Error(err))
		return ErrResetTokenNotFound
	}

	// Проверяем, не истек ли токен
	if passwordReset.ExpiresAt.Before(time.Now()) {
		l.Error("Reset token истек")
		return ErrResetTokenInvalid
	}

	// Проверяем, не использован ли токен
	if passwordReset.Used {
		l.Error("Reset token уже использован")
		return ErrResetTokenInvalid
	}

	// Получаем пользователя
	account, err := s.accountRepo.GetByID(passwordReset.AccountID)
	if err != nil {
		l.Error("Пользователь не найден", zap.Error(err))
		return ErrUserNotFound
	}

	// Хешируем новый пароль
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
	if err != nil {
		l.Error("Ошибка хеширования пароля", zap.Error(err))
		return err
	}

	// Обновляем пароль пользователя
	account.PasswordHash = string(hashedPassword)
	if err := s.accountRepo.Update(account); err != nil {
		l.Error("Ошибка обновления пароля", zap.Error(err))
		return err
	}

	// Помечаем токен как использованный
	passwordReset.Used = true
	if err := s.accountRepo.UpdatePasswordReset(passwordReset); err != nil {
		l.Error("Ошибка обновления статуса токена", zap.Error(err))
		// Не возвращаем ошибку, так как пароль уже изменен
	}

	l.Info("Пароль успешно изменен", zap.Uint("userID", account.Base.ID))
	return nil
}

// RefreshToken обновляет JWT токен по refresh token
func (s *authServiceImpl) RefreshToken(refreshToken string) (*RefreshTokenResponse, error) {
	l := logger.Get()
	l.Info("Начало обновления токена")

	// Парсим и валидируем refresh token
	claims, err := s.validateRefreshToken(refreshToken)
	if err != nil {
		l.Error("Ошибка валидации refresh token", zap.Error(err))
		return nil, err
	}

	// Получаем ID пользователя из claims
	userIDStr, ok := (*claims)["sub"].(string)
	if !ok {
		l.Error("Не удалось получить subject из токена")
		return nil, ErrInvalidToken
	}

	// Конвертируем string в uint
	var userID uint
	if _, err := fmt.Sscan(userIDStr, &userID); err != nil {
		l.Error("Ошибка конвертации user ID", zap.Error(err))
		return nil, ErrInvalidToken
	}

	// Получаем пользователя из базы по ID
	account, err := s.accountRepo.GetByID(userID)
	if err != nil {
		l.Error("Пользователь не найден", zap.Uint("userID", userID), zap.Error(err))
		return nil, ErrUserNotFound
	}

	// Проверяем, активен ли аккаунт
	if !account.IsActive {
		l.Error("Аккаунт деактивирован", zap.Uint("userID", userID))
		return nil, errors.New("account is deactivated")
	}

	// Генерируем новый access token
	accessToken, expiresAt, err := s.generateAccessToken(account)
	if err != nil {
		l.Error("Ошибка генерации access token", zap.Error(err))
		return nil, err
	}

	l.Info("Обновление токена успешно завершено", zap.Uint("userID", userID))
	return &RefreshTokenResponse{
		Token:     accessToken,
		ExpiresAt: expiresAt,
		User: UserInfo{
			ID:        account.Base.ID,
			Email:     account.Email,
			FirstName: account.FirstName,
			LastName:  account.LastName,
			FullName:  account.FullName,
			Role:      account.Role,
		},
	}, nil
}

// Logout выход пользователя
func (s *authServiceImpl) Logout(userID uint) error {
	l := logger.Get()
	l.Info("Выход пользователя", zap.Uint("userID", userID))
	// Здесь можно добавить логику инвалидации токенов (например, в Redis)
	l.Info("Выход успешно завершен", zap.Uint("userID", userID))
	return nil
}

// ValidateAccessToken валидирует access token
func (s *authServiceImpl) ValidateAccessToken(tokenString string) (*jwt.MapClaims, error) {
	token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
		}
		return s.jwtSecret, nil
	})

	if err != nil {
		return nil, err
	}

	if !token.Valid {
		return nil, ErrInvalidToken
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return nil, ErrInvalidToken
	}

	if tokenType, exists := claims["type"]; !exists || tokenType != "access" {
		return nil, ErrInvalidToken
	}

	return &claims, nil
}

// GetUserFromToken получает пользователя из claims
func (s *authServiceImpl) GetUserFromToken(claims *jwt.MapClaims) (*models.Account, error) {
	userIDStr, ok := (*claims)["sub"].(string)
	if !ok {
		return nil, ErrInvalidToken
	}

	var userID uint
	if _, err := fmt.Sscan(userIDStr, &userID); err != nil {
		return nil, ErrInvalidToken
	}

	return s.accountRepo.GetByID(userID)
}

// generateAccessToken генерирует access token
func (s *authServiceImpl) generateAccessToken(account *models.Account) (string, time.Time, error) {
	expiresAt := time.Now().Add(s.accessTokenTTL)

	claims := jwt.MapClaims{
		"sub":   fmt.Sprintf("%d", account.Base.ID),
		"email": account.Email,
		"role":  account.Role,
		"exp":   expiresAt.Unix(),
		"iat":   time.Now().Unix(),
		"type":  "access",
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err := token.SignedString(s.jwtSecret)
	if err != nil {
		return "", time.Time{}, err
	}

	return tokenString, expiresAt, nil
}

// generateRefreshToken генерирует refresh token
func (s *authServiceImpl) generateRefreshToken(account *models.Account) (string, error) {
	expiresAt := time.Now().Add(s.refreshTokenTTL)

	claims := jwt.MapClaims{
		"sub":   fmt.Sprintf("%d", account.Base.ID),
		"exp":   expiresAt.Unix(),
		"iat":   time.Now().Unix(),
		"type":  "refresh",
		"jti":   fmt.Sprintf("refresh-%d-%d", account.Base.ID, time.Now().Unix()),
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenString, err := token.SignedString(s.jwtSecret)
	if err != nil {
		return "", err
	}

	return tokenString, nil
}

// generateResetToken генерирует reset token
func (s *authServiceImpl) generateResetToken() (string, error) {
	// Генерируем случайный токен
	bytes := make([]byte, 32)
	if _, err := rand.Read(bytes); err != nil {
		return "", err
	}
	
	token := base64.URLEncoding.EncodeToString(bytes)
	return token, nil
}

// validateRefreshToken валидирует refresh token
func (s *authServiceImpl) validateRefreshToken(tokenString string) (*jwt.MapClaims, error) {
	token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
		}
		return s.jwtSecret, nil
	})

	if err != nil {
		return nil, err
	}

	if !token.Valid {
		return nil, ErrInvalidToken
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return nil, ErrInvalidToken
	}

	if tokenType, exists := claims["type"]; !exists || tokenType != "refresh" {
		return nil, ErrInvalidToken
	}

	exp, ok := claims["exp"].(float64)
	if ok && int64(exp) < time.Now().Unix() {
		return nil, ErrTokenExpired
	}

	return &claims, nil
}

// GenerateRefreshTokenForUser генерирует новый refresh token для пользователя по ID
func (s *authServiceImpl) GenerateRefreshTokenForUser(userID uint) (string, error) {
	l := logger.Get()
	l.Info("Генерация refresh token для пользователя", zap.Uint("userID", userID))

	// Получаем пользователя из базы по ID
	account, err := s.accountRepo.GetByID(userID)
	if err != nil {
		l.Error("Пользователь не найден", zap.Uint("userID", userID), zap.Error(err))
		return "", ErrUserNotFound
	}

	// Проверяем, активен ли аккаунт
	if !account.IsActive {
		l.Error("Аккаунт деактивирован", zap.Uint("userID", userID))
		return "", errors.New("account is deactivated")
	}

	// Генерируем refresh token
	refreshToken, err := s.generateRefreshToken(account)
	if err != nil {
		l.Error("Ошибка генерации refresh token", zap.Error(err))
		return "", err
	}

	l.Info("Refresh token успешно сгенерирован", zap.Uint("userID", userID))
	return refreshToken, nil
}