package users_service

import (
	"context"
	"errors"
	"evening_detective_server/internal/modules/email_sender"
	"evening_detective_server/internal/modules/password_generator"
	"evening_detective_server/internal/modules/processor_jwt"
	"evening_detective_server/internal/modules/roles"
	"evening_detective_server/internal/repos"
	"evening_detective_server/internal/repos/refresh_tokens_repo"
	"evening_detective_server/internal/repos/users_repo"
	"fmt"
	"slices"
	"time"

	"golang.org/x/crypto/bcrypt"
)

type UsersService struct {
	usersRepo         *users_repo.UsersRepo
	passwordGenerator password_generator.IPasswordGenerator
	emailSender       email_sender.IEmailSender
	processorJWT      processor_jwt.IProcessorJWT
	refreshTokensRepo *refresh_tokens_repo.RefreshTokensRepo
}

func NewUsersService(
	usersRepo *users_repo.UsersRepo,
	passwordGenerator password_generator.IPasswordGenerator,
	emailSender email_sender.IEmailSender,
	processorJWT processor_jwt.IProcessorJWT,
	refreshTokensRepo *refresh_tokens_repo.RefreshTokensRepo,
) *UsersService {
	return &UsersService{
		usersRepo:         usersRepo,
		passwordGenerator: passwordGenerator,
		emailSender:       emailSender,
		processorJWT:      processorJWT,
		refreshTokensRepo: refreshTokensRepo,
	}
}

func (s *UsersService) AddUser(
	ctx context.Context,
	username string,
	email string,
) error {
	password, err := s.passwordGenerator.Generate()
	if err != nil {
		return err
	}
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}
	err = s.usersRepo.AddUser(
		ctx,
		username,
		email,
		string(hashedPassword),
		[]string{"user"},
	)
	if err != nil {
		return err
	}

	err = s.emailSender.Send(ctx, email_sender.Message{
		To:      email,
		Subject: "Приветствую тебя, детектив!",
		Body:    fmt.Sprintf("Вот твой пароль для входа в систему: %s", password),
	})

	return err
}

func (s *UsersService) RefreshPassword(
	ctx context.Context,
	email string,
) error {
	password, err := s.passwordGenerator.Generate()
	if err != nil {
		return err
	}
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return err
	}

	err = s.usersRepo.UpdateUserPassword(
		ctx,
		email,
		string(hashedPassword),
	)
	if err != nil {
		return err
	}

	err = s.emailSender.Send(ctx, email_sender.Message{
		To:      email,
		Subject: "Приветствую тебя, детектив!",
		Body:    fmt.Sprintf("Вот твой новый пароль для входа в систему, не теряй: %s", password),
	})

	return err
}

func (s *UsersService) Login(
	ctx context.Context,
	email string,
	password string,
) (string, string, error) {
	user, err := s.usersRepo.GetUserByEmail(
		ctx,
		email,
	)
	if err != nil {
		return "", "", err
	}

	if err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
		return "", "", errors.New("Не верен email или пароль")
	}

	accessToken, err := s.processorJWT.GenerateAccessToken(user.ID, user.Email, user.Roles)
	if err != nil {
		return "", "", errors.New("Не получилось получить доступ")
	}

	refreshToken, err := s.processorJWT.GenerateRefreshToken(user.ID)
	if err != nil {
		return "", "", errors.New("Не получилось получить доступ")
	}
	refreshTokenClaims, err := s.processorJWT.GetClaims(refreshToken)
	if err != nil {
		return "", "", errors.New("Не получилось получить доступ")
	}

	if err := s.refreshTokensRepo.AddRefreshToken(ctx, user.ID, refreshTokenClaims.ExpiresAt.Time, refreshTokenClaims.ID); err != nil {
		return "", "", err
	}

	return accessToken, refreshToken, nil
}

func (s *UsersService) Refresh(
	ctx context.Context,
	token string,
) (string, string, error) {
	claims, err := s.processorJWT.GetClaims(token)
	if err != nil {
		return "", "", errors.New("Пользователь не найден")
	}

	now := time.Now()
	ok, err := s.refreshTokensRepo.ExistsRefreshToken(ctx, claims.UserID, claims.ID, now)
	if err != nil {
		return "", "", err
	}
	if !ok {
		return "", "", errors.New("Пользователь не найден")
	}
	if err := s.refreshTokensRepo.DeleteRefreshTokenByJti(ctx, claims.ID); err != nil {
		return "", "", err
	}

	user, err := s.usersRepo.GetUserByID(ctx, claims.UserID)
	if err != nil {
		return "", "", err
	}

	accessToken, err := s.processorJWT.GenerateAccessToken(user.ID, user.Email, user.Roles)
	if err != nil {
		return "", "", errors.New("Не получилось получить доступ")
	}

	refreshToken, err := s.processorJWT.GenerateRefreshToken(user.ID)
	if err != nil {
		return "", "", errors.New("Не получилось получить доступ")
	}
	refreshTokenClaims, err := s.processorJWT.GetClaims(refreshToken)
	if err != nil {
		return "", "", errors.New("Не получилось получить доступ")
	}

	if err := s.refreshTokensRepo.AddRefreshToken(ctx, user.ID, refreshTokenClaims.ExpiresAt.Time, refreshTokenClaims.ID); err != nil {
		return "", "", err
	}

	return accessToken, refreshToken, nil
}

func (s *UsersService) GetUsers(
	ctx context.Context,
) ([]*repos.User, error) {
	users, err := s.usersRepo.GetUsers(ctx)
	if err != nil {
		return nil, err
	}
	return users, nil
}

func (s *UsersService) GetUserByID(
	ctx context.Context,
	userId int,
) (*repos.User, error) {
	user, err := s.usersRepo.GetUserByID(ctx, userId)
	if err != nil {
		return nil, err
	}
	return user, nil
}

func (s *UsersService) AddUserRole(
	ctx context.Context,
	userId int,
	role string,
) error {
	if !slices.Contains(roles.AllRoles, role) {
		return fmt.Errorf("role %s not support", role)
	}
	return s.usersRepo.AddUserRole(ctx, userId, role)
}

func (s *UsersService) DeleteUserRole(
	ctx context.Context,
	userId int,
	role string,
) error {
	if !slices.Contains(roles.AllRoles, role) {
		return fmt.Errorf("role %s not support", role)
	}
	return s.usersRepo.DeleteUserRole(ctx, userId, role)
}
