package service

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"time"

	"git.kirlllll.ru/volontery/backend/internal/database"
	"git.kirlllll.ru/volontery/backend/internal/pkg/jwt"
	"git.kirlllll.ru/volontery/backend/internal/pkg/password"
	"git.kirlllll.ru/volontery/backend/internal/repository"
)

// AuthService предоставляет методы для аутентификации
type AuthService struct {
	userRepo *repository.UserRepository
	authRepo *repository.AuthRepository
	rbacRepo *repository.RBACRepository
	jwtMgr   *jwt.Manager
}

// NewAuthService создает новый AuthService
func NewAuthService(
	userRepo *repository.UserRepository,
	authRepo *repository.AuthRepository,
	rbacRepo *repository.RBACRepository,
	jwtMgr *jwt.Manager,
) *AuthService {
	return &AuthService{
		userRepo: userRepo,
		authRepo: authRepo,
		rbacRepo: rbacRepo,
		jwtMgr:   jwtMgr,
	}
}

// RegisterRequest - запрос на регистрацию
type RegisterRequest struct {
	Email     string  `json:"email"`
	Password  string  `json:"password"`
	FirstName string  `json:"first_name"`
	LastName  string  `json:"last_name"`
	Phone     string  `json:"phone,omitempty"`
	Latitude  float64 `json:"latitude,omitempty"`
	Longitude float64 `json:"longitude,omitempty"`
	Address   string  `json:"address,omitempty"`
	City      string  `json:"city,omitempty"`
	Bio       string  `json:"bio,omitempty"`
}

// LoginRequest - запрос на вход
type LoginRequest struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

// AuthResponse - ответ с токенами
type AuthResponse struct {
	AccessToken  string    `json:"access_token"`
	RefreshToken string    `json:"refresh_token"`
	ExpiresIn    int64     `json:"expires_in"`
	User         *UserInfo `json:"user"`
}

// UserInfo - информация о пользователе
type UserInfo struct {
	ID        int64  `json:"id"`
	Email     string `json:"email"`
	FirstName string `json:"first_name"`
	LastName  string `json:"last_name"`
	Verified  bool   `json:"email_verified"`
}

// Register регистрирует нового пользователя
func (s *AuthService) Register(ctx context.Context, req RegisterRequest) (*AuthResponse, error) {
	// Валидация
	if req.Email == "" {
		return nil, fmt.Errorf("email is required")
	}
	if !password.IsValid(req.Password) {
		return nil, fmt.Errorf("password must be at least 8 characters")
	}
	if req.FirstName == "" || req.LastName == "" {
		return nil, fmt.Errorf("first name and last name are required")
	}

	// Проверка существования email
	exists, err := s.userRepo.EmailExists(ctx, req.Email)
	if err != nil {
		return nil, fmt.Errorf("failed to check email: %w", err)
	}
	if exists {
		return nil, fmt.Errorf("email already registered")
	}

	// Хеширование пароля
	hashedPassword, err := password.Hash(req.Password)
	if err != nil {
		return nil, fmt.Errorf("failed to hash password: %w", err)
	}

	// Создание пользователя
	user, err := s.userRepo.Create(ctx, database.CreateUserParams{
		Email:         req.Email,
		PasswordHash:  hashedPassword,
		FirstName:     req.FirstName,
		LastName:      req.LastName,
		Phone:         stringToPgText(req.Phone),
		StMakepoint:   req.Longitude,
		StMakepoint_2: req.Latitude,
		Address:       stringToPgText(req.Address),
		City:          stringToPgText(req.City),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create user: %w", err)
	}

	// Назначение роли "requester" по умолчанию
	requesterRole, err := s.rbacRepo.GetRoleByName(ctx, "requester")
	if err == nil {
		_, _ = s.rbacRepo.AssignRoleToUser(ctx, database.AssignRoleToUserParams{
			UserID:     user.ID,
			RoleID:     requesterRole.ID,
			AssignedBy: int64ToPgInt8(user.ID), // сам себе назначил
		})
	}

	// Генерация токенов
	return s.generateTokens(ctx, user.ID, user.Email, "", "")
}

// Login выполняет вход пользователя
func (s *AuthService) Login(ctx context.Context, req LoginRequest) (*AuthResponse, error) {
	// Получение пользователя
	user, err := s.userRepo.GetByEmail(ctx, req.Email)
	if err != nil {
		return nil, fmt.Errorf("invalid email or password")
	}

	// Проверка пароля
	if err := password.Verify(user.PasswordHash, req.Password); err != nil {
		return nil, fmt.Errorf("invalid email or password")
	}

	// Проверка блокировки
	if user.IsBlocked.Bool {
		return nil, fmt.Errorf("user account is blocked")
	}

	// Обновление времени последнего входа
	_ = s.userRepo.UpdateLastLogin(ctx, user.ID)

	// Генерация токенов
	return s.generateTokens(ctx, user.ID, user.Email, "", "")
}

// RefreshTokens обновляет токены
func (s *AuthService) RefreshTokens(ctx context.Context, refreshTokenString string) (*AuthResponse, error) {
	// Валидация refresh токена
	claims, err := s.jwtMgr.ValidateToken(refreshTokenString)
	if err != nil {
		return nil, fmt.Errorf("invalid refresh token")
	}

	// Проверка токена в БД
	storedToken, err := s.authRepo.GetRefreshToken(ctx, refreshTokenString)
	if err != nil {
		return nil, fmt.Errorf("refresh token not found or expired")
	}

	// Отзыв старого токена
	_ = s.authRepo.RevokeRefreshToken(ctx, storedToken.ID)

	// Получение пользователя
	user, err := s.userRepo.GetByID(ctx, claims.UserID)
	if err != nil {
		return nil, fmt.Errorf("user not found")
	}

	if user.IsBlocked.Bool {
		return nil, fmt.Errorf("user account is blocked")
	}

	// Генерация новых токенов
	return s.generateTokens(ctx, user.ID, user.Email, "", "")
}

// Logout выход пользователя
func (s *AuthService) Logout(ctx context.Context, userID int64) error {
	return s.authRepo.RevokeAllUserTokens(ctx, userID)
}

// generateTokens генерирует access и refresh токены
func (s *AuthService) generateTokens(ctx context.Context, userID int64, email, userAgent, ipAddress string) (*AuthResponse, error) {
	// Генерация access токена
	accessToken, err := s.jwtMgr.GenerateAccessToken(userID, email)
	if err != nil {
		return nil, fmt.Errorf("failed to generate access token: %w", err)
	}

	// Генерация refresh токена
	refreshToken, err := s.jwtMgr.GenerateRefreshToken(userID, email)
	if err != nil {
		return nil, fmt.Errorf("failed to generate refresh token: %w", err)
	}

	// Сохранение refresh токена в БД
	expiresAt := time.Now().Add(s.jwtMgr.GetRefreshTokenDuration())
	_, err = s.authRepo.CreateRefreshToken(ctx, database.CreateRefreshTokenParams{
		UserID:    userID,
		Token:     refreshToken,
		ExpiresAt: timeToPgTimestamptz(expiresAt),
		UserAgent: stringToPgText(userAgent),
		IpAddress: nil, // IP адрес не передается в текущей реализации
	})
	if err != nil {
		return nil, fmt.Errorf("failed to save refresh token: %w", err)
	}

	// Получение информации о пользователе
	user, _ := s.userRepo.GetByID(ctx, userID)

	return &AuthResponse{
		AccessToken:  accessToken,
		RefreshToken: refreshToken,
		ExpiresIn:    int64(s.jwtMgr.GetAccessTokenDuration().Seconds()),
		User: &UserInfo{
			ID:        userID,
			Email:     email,
			FirstName: user.FirstName,
			LastName:  user.LastName,
			Verified:  user.EmailVerified.Bool,
		},
	}, nil
}

// generateRandomToken генерирует случайный токен
func generateRandomToken(length int) (string, error) {
	bytes := make([]byte, length)
	if _, err := rand.Read(bytes); err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(bytes), nil
}
