package keycloak

import (
	"context"
	"errors"
	"time"

	"github.com/golang-jwt/jwt/v5"

	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/auth"
	"github.com/Nerzal/gocloak/v13"
)

func (k *Client) RegisterUser(ctx context.Context, request auth.RegisterUserRequest) (*auth.RegisterUserResponse, error) {
	attrs := make(map[string][]string)

	if request.User.Patronymic != nil {
		attrs[PatronymicAttr] = []string{*request.User.Patronymic}
	}

	for key, value := range request.User.Permissions {
		attrs[key] = []string{value}
	}

	for _, perm := range permissionsSlice {
		if _, ok := attrs[perm]; !ok {
			return nil, errors.Join(ErrBadRequest, ErrMissingPermission)
		}
	}

	client, err := k.client.LoginClient(ctx, k.clientId, k.clientSecret, k.realm)
	if err != nil {
		return nil, k.handleError(err)
	}

	userGroup, err := k.getUserGroupByType(request.UserType)
	if err != nil {
		return nil, err
	}

	emailVerified := false
	if k.env != "production" {
		emailVerified = true
	}

	userId, err := k.client.CreateUser(
		ctx,
		client.AccessToken,
		k.realm,
		gocloak.User{
			Email:         gocloak.StringP(request.User.Email),
			EmailVerified: &emailVerified,
			FirstName:     gocloak.StringP(request.User.FirstName),
			LastName:      gocloak.StringP(request.User.SecondName),
			Groups:        &[]string{userGroup},
			Enabled:       gocloak.BoolP(true),
			Attributes:    &attrs,
		})
	if err != nil {
		return nil, k.handleError(err)
	}

	if err = k.client.SetPassword(
		ctx, client.AccessToken,
		userId, k.realm,
		request.User.Password, false,
	); err != nil {
		return nil, k.handleError(err)
	}

	return &auth.RegisterUserResponse{
		UserId: userId,
	}, nil
}

func (k *Client) getUserGroupByType(userType int32) (string, error) {
	switch userType {
	case UserAgentType:
		return UserGroupAgents, nil
	case UserDistributorType:
		return UserGroupDistributor, nil
	default:
		return "", ErrUnknownUserType
	}
}

func (k *Client) LoginUser(ctx context.Context, request auth.LoginUserRequest) (*auth.LoginUserResponse, error) {
	token, err := k.client.Login(ctx, k.clientId, k.clientSecret, k.realm, request.Email, request.Password)
	if err != nil {
		return nil, k.handleError(err)
	}

	userInfo, err := k.jwtManager.GetUserInfoFromToken(token.AccessToken)
	if err != nil {
		return nil, k.handleError(err)
	}

	return &auth.LoginUserResponse{
		UserId:       userInfo.UserId,
		AccessToken:  token.AccessToken,
		RefreshToken: token.RefreshToken,
	}, nil
}

func (k *Client) GetNewAccessToken(ctx context.Context, request auth.GetNewAccessTokenRequest) (*auth.GetNewAccessTokenResponse, error) {
	token, err := k.client.RefreshToken(ctx, request.RefreshToken, k.clientId, k.clientSecret, k.realm)
	if err != nil {
		return nil, k.handleError(err)
	}

	return &auth.GetNewAccessTokenResponse{
		AccessToken:  token.AccessToken,
		RefreshToken: token.RefreshToken,
	}, nil
}

func (k *Client) CheckTokenIsValid(ctx context.Context, token string) (bool, error) {
	resp, err := k.client.RetrospectToken(ctx, token, k.clientId, k.clientSecret, k.realm)
	if err != nil {
		return false, k.handleError(err)
	}

	if resp.Active == nil {
		return false, nil
	}

	return *resp.Active, nil
}

func (k *Client) LogoutUser(ctx context.Context, request auth.LogoutUserRequest) error {
	if err := k.client.Logout(ctx, k.clientId, k.clientSecret, k.realm, request.RefreshToken); err != nil {
		return k.handleError(err)
	}

	if err := k.client.RevokeToken(ctx, k.realm, k.clientId, k.clientSecret, request.RefreshToken); err != nil {
		return k.handleError(err)
	}

	return nil
}

func (k *Client) GetUserInfo(ctx context.Context, id string) (*auth.UserInfo, error) {
	client, err := k.client.LoginClient(ctx, k.clientId, k.clientSecret, k.realm)
	if err != nil {
		return nil, k.handleError(err)
	}

	user, err := k.client.GetUserByID(ctx, client.AccessToken, k.realm, id)
	if err != nil {
		return nil, k.handleError(err)
	}

	if user.Attributes == nil {
		return nil, ErrNoAttributes
	}

	groups, err := k.client.GetUserGroups(ctx, client.AccessToken, k.realm, id, gocloak.GetGroupsParams{})
	if err != nil {
		return nil, k.handleError(err)
	}

	if len(groups) == 0 {
		return nil, ErrUnknownUserType
	}

	if groups[0].Name == nil {
		return nil, ErrUnknownUserType
	}

	userType, err := k.getUserTypeByUserGroup(*groups[0].Name)
	if err != nil {
		return nil, err
	}

	return &auth.UserInfo{
		Email:       *user.Email,
		FirstName:   *user.FirstName,
		SecondName:  *user.LastName,
		Patronymic:  k.getUserPatronymicFromAttributes(*user.Attributes),
		Permissions: k.getUserPermissionsFromAttributes(*user.Attributes),
		UserType:    userType,
	}, nil
}

func (k *Client) getUserPatronymicFromAttributes(attr attributes) *string {
	item, ok := attr[PatronymicAttr]
	if !ok || len(item) == 0 {
		return nil
	}

	return &item[0]
}

func (k *Client) getUserPermissionsFromAttributes(attr attributes) permissions {
	resp := make(map[string]string)

	for _, permission := range permissionsSlice {
		if item, ok := attr[permission]; ok {
			if len(item) != 0 {
				resp[permission] = item[0]
			} else {
				resp[permission] = "" // todo: подумать над тем как правильно делать в таких ситуациях
			}
		} else {
			resp[permission] = "" // todo: подумать над тем как правильно делать в таких ситуациях
		}
	}

	return resp
}

func (k *Client) getUserTypeByUserGroup(attr string) (int32, error) {
	switch attr {
	case UserGroupAgents:
		return UserAgentType, nil
	case UserGroupDistributor:
		return UserDistributorType, nil
	default:
		return -1, ErrUnknownUserType
	}
}

func (k *Client) GetPermissionsByUsersId(ctx context.Context, id string) (*auth.GetPermissionsByUsersIdResponse, error) {
	userInfo, err := k.GetUserInfo(ctx, id)
	if err != nil {
		return nil, err
	}

	return &auth.GetPermissionsByUsersIdResponse{
		Balance:     userInfo.Permissions[PermissionBalance],
		Company:     userInfo.Permissions[PermissionCompany],
		Employees:   userInfo.Permissions[PermissionEmployees],
		Profile:     userInfo.Permissions[PermissionProfile],
		Submissions: userInfo.Permissions[PermissionSubmissions],
		Vacancies:   userInfo.Permissions[PermissionVacancies],
	}, nil
}

func (k *Client) CheckPermissions(ctx context.Context, request auth.CheckPermissionsRequest) (bool, error) {
	userInfo, err := k.GetUserInfo(ctx, request.UserId)
	if err != nil {
		return false, err
	}

	if userInfo.Permissions == nil {
		return false, ErrInternal
	}

	if val, ok := userInfo.Permissions[request.RequiredPermission]; ok {
		return k.getPermissionLevel(val) >= k.getPermissionLevel(request.RequiredPermissionLevel), nil
	}

	return false, ErrInternal
}

func (k *Client) getPermissionLevel(permission string) int8 {
	switch permission {
	case PermissionLevelCanEdit:
		return 2
	case PermissionLevelCanView:
		return 1
	case PermissionLevelNoPermission:
		return 0
	default:
		return -1
	}
}

func (k *Client) VerifyEmail(ctx context.Context, userID string, token string) error {
	if err := k.jwtManager.VerifyEmailToken(token, userID); err != nil {
		return err
	}

	// Get admin token
	adminToken, err := k.client.LoginClient(ctx, k.clientId, k.clientSecret, k.realm)
	if err != nil {
		return k.handleError(err)
	}

	// Get user
	user, err := k.client.GetUserByID(ctx, adminToken.AccessToken, k.realm, userID)
	if err != nil {
		return k.handleError(err)
	}

	// Set email as verified
	user.EmailVerified = gocloak.BoolP(true)

	// Update user
	if err := k.client.UpdateUser(ctx, adminToken.AccessToken, k.realm, *user); err != nil {
		return k.handleError(err)
	}

	return nil
}

func (k *Client) GetEmailVerificationStatus(ctx context.Context, userID string) (*auth.GetEmailVerificationResponse, error) {
	// Get admin token
	adminToken, err := k.client.LoginClient(ctx, k.clientId, k.clientSecret, k.realm)
	if err != nil {
		return nil, k.handleError(err)
	}

	// Get user
	user, err := k.client.GetUserByID(ctx, adminToken.AccessToken, k.realm, userID)
	if err != nil {
		return nil, k.handleError(err)
	}

	// Check if email is verified
	verified := false
	if user.EmailVerified != nil {
		verified = *user.EmailVerified
	}

	return &auth.GetEmailVerificationResponse{
		EmailVerified: verified,
	}, nil
}

func (k *Client) GetUserEmailVerificationToken(ctx context.Context, uid string) (*auth.GetUserTokenResponse, error) {
	// Получение токена администратора
	adminToken, err := k.client.LoginClient(ctx, k.clientId, k.clientSecret, k.realm)
	if err != nil {
		return nil, k.handleError(err)
	}

	// Получаем пользователя, чтобы убедиться в его существовании
	_, err = k.client.GetUserByID(ctx, adminToken.AccessToken, k.realm, uid)
	if err != nil {
		return nil, k.handleError(err)
	}

	// Создаём кастомный JWT-токен для верификации email сроком на 24 часа
	claims := jwt.MapClaims{
		"sub":     uid,                                   // Subject (user ID)
		"iat":     time.Now().Unix(),                     // Issued at
		"exp":     time.Now().Add(24 * time.Hour).Unix(), // Expires in 24 hours
		"purpose": "email_verification",                  // Custom claim to identify purpose
		"iss":     "molva-api-gateway",                   // Issuer
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// Используем клиентский секрет как ключ подписи
	signedToken, err := token.SignedString([]byte(k.clientSecret))
	if err != nil {
		return nil, ErrInternal
	}

	return &auth.GetUserTokenResponse{
		AccessToken: signedToken,
	}, nil
}

func (k *Client) ResetPassword(ctx context.Context, request auth.ResetPasswordRequest) error {
	token, err := k.client.LoginClient(ctx, k.clientId, k.clientSecret, k.realm)
	if err != nil {
		return k.handleError(err)
	}

	user, err := k.client.GetUsers(
		ctx,
		token.AccessToken,
		k.realm,
		gocloak.GetUsersParams{
			Username: gocloak.StringP(request.Email),
		},
	)
	if err != nil {
		return k.handleError(err)
	}

	if len(user) == 0 {
		return ErrNotFound
	}

	if err := k.client.SetPassword(
		ctx,
		token.AccessToken,
		*user[0].ID,
		k.realm,
		request.NewPassword,
		false,
	); err != nil {
		return k.handleError(err)
	}

	return nil
}
