package service

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"strings"
	"time"
	"web-qudo-be/app/database/entity"
	"web-qudo-be/app/database/entity/users"
	userLevelsRepository "web-qudo-be/app/module/user_levels/repository"
	"web-qudo-be/app/module/users/mapper"
	"web-qudo-be/app/module/users/repository"
	"web-qudo-be/app/module/users/request"
	"web-qudo-be/app/module/users/response"
	"web-qudo-be/config/config"
	"web-qudo-be/utils/paginator"
	utilSvc "web-qudo-be/utils/service"

	paseto "aidanwoods.dev/go-paseto"
	"github.com/Nerzal/gocloak/v13"
	"github.com/google/uuid"
	"github.com/rs/zerolog"
)

// UsersService
type usersService struct {
	Repo           repository.UsersRepository
	UserLevelsRepo userLevelsRepository.UserLevelsRepository
	Log            zerolog.Logger
	Keycloak       *config.KeycloakConfig
	Smtp           *config.SmtpConfig
}

// UsersService define interface of IUsersService
type UsersService interface {
	All(clientId *uuid.UUID, req request.UsersQueryRequest) (users []*response.UsersResponse, paging paginator.Pagination, err error)
	Show(clientId *uuid.UUID, id uint) (users *response.UsersResponse, err error)
	ShowByUsername(clientId *uuid.UUID, username string) (users *response.UsersResponse, err error)
	ShowUserInfo(clientId *uuid.UUID, authToken string) (users *response.UsersResponse, err error)
	Save(clientId *uuid.UUID, req request.UsersCreateRequest, authToken string) (userReturn *users.Users, err error)
	Login(req request.UserLogin) (res *gocloak.JWT, err error)
	ParetoLogin(req request.UserLogin) (res *response.ParetoLoginResponse, err error)
	Update(clientId *uuid.UUID, id uint, req request.UsersUpdateRequest) (err error)
	Delete(clientId *uuid.UUID, id uint) error
	SavePassword(clientId *uuid.UUID, req request.UserSavePassword, authToken string) (err error)
	ResetPassword(req request.UserResetPassword) (err error)
	ForgotPassword(clientId *uuid.UUID, req request.UserForgotPassword) (err error)
	EmailValidationPreLogin(clientId *uuid.UUID, req request.UserEmailValidationRequest) (msgResponse *string, err error)
	SetupEmail(clientId *uuid.UUID, req request.UserEmailValidationRequest) (msgResponse *string, err error)
	OtpRequest(req request.UserOtpRequest) (err error)
	OtpValidation(req request.UserOtpValidation) (err error)
	SendLoginOtp(name string, email string, otp string) error
	SendRegistrationOtp(name string, email string, otp string) error
}

// NewUsersService init UsersService
func NewUsersService(repo repository.UsersRepository, userLevelsRepo userLevelsRepository.UserLevelsRepository, log zerolog.Logger, keycloak *config.KeycloakConfig, smtp *config.SmtpConfig) UsersService {

	return &usersService{
		Repo:           repo,
		UserLevelsRepo: userLevelsRepo,
		Log:            log,
		Keycloak:       keycloak,
		Smtp:           smtp,
	}
}

// All implement interface of UsersService
func (_i *usersService) All(clientId *uuid.UUID, req request.UsersQueryRequest) (users []*response.UsersResponse, paging paginator.Pagination, err error) {
	results, paging, err := _i.Repo.GetAll(clientId, req)
	if err != nil {
		return
	}

	for _, result := range results {
		users = append(users, mapper.UsersResponseMapper(result, _i.UserLevelsRepo, clientId))
	}

	return
}

func (_i *usersService) Show(clientId *uuid.UUID, id uint) (users *response.UsersResponse, err error) {
	result, err := _i.Repo.FindOne(clientId, id)
	if err != nil {
		return nil, err
	}

	return mapper.UsersResponseMapper(result, _i.UserLevelsRepo, clientId), nil
}

func (_i *usersService) ShowByUsername(clientId *uuid.UUID, username string) (users *response.UsersResponse, err error) {
	result, err := _i.Repo.FindByUsername(clientId, username)
	if err != nil {
		return nil, err
	}

	return mapper.UsersResponseMapper(result, _i.UserLevelsRepo, clientId), nil
}

func (_i *usersService) ShowUserInfo(clientId *uuid.UUID, authToken string) (users *response.UsersResponse, err error) {
	userInfo := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)

	return mapper.UsersResponseMapper(userInfo, _i.UserLevelsRepo, clientId), nil
}

func (_i *usersService) Save(clientId *uuid.UUID, req request.UsersCreateRequest, authToken string) (userReturn *users.Users, err error) {
	_i.Log.Info().Interface("data", req).Msg("")
	newReq := req.ToEntity()

	_i.Log.Info().Interface("AUTH TOKEN", authToken).Msg("")

	if authToken != "" {
		createdBy := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)
		newReq.CreatedById = &createdBy.ID
	}

	keycloakId, err := _i.Keycloak.CreateUser(req.Fullname, req.Email, req.Username, req.Password)
	if err != nil {
		return nil, err
	}

	newReq.KeycloakId = &keycloakId
	newReq.TempPassword = &req.Password

	// Set ClientId on entity
	newReq.ClientId = clientId

	return _i.Repo.Create(newReq)
}

func (_i *usersService) Login(req request.UserLogin) (res *gocloak.JWT, err error) {
	_i.Log.Info().Interface("data", req).Msg("")

	var loginResponse *gocloak.JWT
	if req.RefreshToken == nil {
		loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password)
	} else {
		loginResponse, err = _i.Keycloak.RefreshToken(*req.RefreshToken)
	}
	if err != nil {
		return nil, err
	}
	return loginResponse, nil
}

func (_i *usersService) ParetoLogin(req request.UserLogin) (res *response.ParetoLoginResponse, err error) {
	_i.Log.Info().Interface("data", req).Msg("")

	var loginResponse *gocloak.JWT
	token := paseto.NewToken()
	secretKeyHex := "bdc42b1a0ba2bac3e27ba84241f9de06dee71b70f838af8d1beb0417f03d1d00"
	secretKey, _ := paseto.V4SymmetricKeyFromHex(secretKeyHex)
	// secretKey := paseto.NewV4SymmetricKey() // to change the secretKey periodically

	if req.RefreshToken == nil {
		loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password)
	} else {
		// Retrieve Refresh Token
		parser := paseto.NewParser()
		verifiedToken, err := parser.ParseV4Local(secretKey, *req.RefreshToken, nil)
		if err != nil {
			panic(err)
		}

		refreshToken, _ := verifiedToken.GetString("refresh_token")
		_i.Log.Info().Interface("Pareto parse refresh token", refreshToken).Msg("")

		loginResponse, err = _i.Keycloak.RefreshToken(refreshToken)
	}

	_i.Log.Info().Interface("loginResponse", loginResponse).Msg("")

	if err != nil {
		return nil, err
	}

	parseToken, err := ParseJWTToken(loginResponse.AccessToken)
	if err != nil {
		return nil, err
	}

	issuedAt := parseToken["iat"].(float64)
	expirationTime := parseToken["exp"].(float64)
	issuer := parseToken["iss"].(string)
	jti := parseToken["jti"].(string)
	subject := parseToken["sub"].(string)

	token.SetIssuedAt(time.Unix(int64(issuedAt), 0))
	token.SetNotBefore(time.Unix(int64(issuedAt), 0))
	token.SetExpiration(time.Unix(int64(expirationTime), 0))
	token.SetIssuer(issuer)
	token.SetJti(jti)
	token.SetSubject(subject)
	token.SetString("access_token", loginResponse.AccessToken)
	token.SetString("refresh_token", loginResponse.RefreshToken)

	_i.Log.Info().Interface("Pareto Generated Key", secretKey.ExportHex()).Msg("")

	tokenEncrypted := token.V4Encrypt(secretKey, nil)

	parser := paseto.NewParser()
	verifiedToken, err := parser.ParseV4Local(secretKey, tokenEncrypted, nil)
	if err != nil {
		panic(err)
	}

	tokenParsing, _ := verifiedToken.GetString("access_token")
	_i.Log.Info().Interface("Pareto parse token", tokenParsing).Msg("")

	resLogin := &response.ParetoLoginResponse{
		AccessToken: tokenEncrypted,
	}

	if err != nil {
		return nil, err
	}

	return resLogin, nil
}

func (_i *usersService) Update(clientId *uuid.UUID, id uint, req request.UsersUpdateRequest) (err error) {
	_i.Log.Info().Interface("data", req).Msg("")
	newReq := req.ToEntity()

	findUser, err := _i.Repo.FindOne(clientId, id)
	if err != nil {
		return err
	}

	err = _i.Keycloak.UpdateUser(findUser.KeycloakId, req.Fullname, req.Email)
	if err != nil {
		return err
	}

	// Set ClientId on entity
	newReq.ClientId = clientId

	return _i.Repo.Update(clientId, id, newReq)
}

func (_i *usersService) Delete(clientId *uuid.UUID, id uint) error {
	result, err := _i.Repo.FindOne(clientId, id)
	if err != nil {
		return err
	}

	isActive := false
	result.IsActive = &isActive
	return _i.Repo.Update(clientId, id, result)
}

func (_i *usersService) SavePassword(clientId *uuid.UUID, req request.UserSavePassword, authToken string) (err error) {
	_i.Log.Info().Interface("data", req).Msg("")

	_i.Log.Info().Interface("AUTH TOKEN", authToken).Msg("")

	if authToken != "" {
		createdBy := utilSvc.GetUserInfo(_i.Log, _i.Repo, authToken)

		tokenString := strings.TrimPrefix(authToken, "Bearer ")

		err := _i.Keycloak.SetPassword(tokenString, *createdBy.KeycloakId, req.Password)
		if err != nil {
			return err
		}

		return nil
	} else {
		return fmt.Errorf("Invalid token")
	}
}

func (_i *usersService) ResetPassword(req request.UserResetPassword) (err error) {
	_i.Log.Info().Interface("data", req).Msg("")

	if req.Password != req.ConfirmPassword {
		return fmt.Errorf("Invalid Password")
	}

	user, err := _i.Repo.FindByKeycloakId(req.UserId)
	if err != nil {
		return fmt.Errorf("User Id Not Found")
	}

	forgotPassword, err := _i.Repo.FindForgotPassword(req.UserId, req.CodeRequest)
	if err != nil {
		return fmt.Errorf("Invalid Request")
	}

	_i.Log.Info().Interface("data", forgotPassword).Msg("")

	_i.Log.Info().Interface("dataForgotPassword", forgotPassword).Msg("")

	if user != nil {
		err := _i.Keycloak.SetPasswordWithoutToken(req.UserId, req.Password)
		if err != nil {
			return err
		}

		forgotPassword.IsActive = false
		forgotPassword.UpdatedAt = time.Now()
		err = _i.Repo.UpdateForgotPassword(forgotPassword.ID, forgotPassword)
		if err != nil {
			return err
		}

		return nil
	} else {
		return fmt.Errorf("User not found")
	}
}

func (_i *usersService) ForgotPassword(clientId *uuid.UUID, req request.UserForgotPassword) (err error) {
	_i.Log.Info().Interface("data", req).Msg("")

	user, err := _i.Repo.FindByUsername(clientId, req.Username)
	if err != nil {
		return err
	}

	if user != nil {
		codeRequest, err := utilSvc.GenerateNumericCode(8)
		if err != nil {
			return nil
		}
		forgotPasswordReq := entity.ForgotPasswords{
			KeycloakID:  *user.KeycloakId,
			CodeRequest: codeRequest,
			IsActive:    true,
		}

		err = _i.Repo.CreateForgotPassword(&forgotPasswordReq)
		if err != nil {
			return err
		}

		// send email forgot password
		url := fmt.Sprintf("https://kontenhumas.com/setup-password?userId=%s&code=%s", *user.KeycloakId, codeRequest)

		subject := "[HUMAS POLRI] Forgot Password"
		htmlBody := "<p>Anda telah mengirimkan permintaan untuk melakukan reset password.</p>"
		htmlBody += "<p style='padding-bottom: 10px;'>Silahkan buat password akun anda dengan menekan tombol di bawah ini, untuk membuat password baru</p>"
		htmlBody += fmt.Sprintf("<a style='padding:8px 50px;border-radius:28px;background-color:#37c2b6;text-decoration:none;color:#f4f5f5;' href='%s'>Reset Password</a>", url)
		htmlBody += "<p style='padding-top: 10px;'>Terimakasih.</p>"
		err = _i.Smtp.SendEmail(subject, user.Email, user.Fullname, htmlBody)

		return nil
	} else {
		return fmt.Errorf("User not found")
	}
}

func (_i *usersService) OtpRequest(req request.UserOtpRequest) (err error) {
	_i.Log.Info().Interface("data", req).Msg("")

	codeRequest, err := utilSvc.GenerateNumericCode(6)
	if req.Name == nil {
		req.Name = &req.Email
	}
	if err != nil {
		return err
	}
	otpReq := entity.OneTimePasswords{
		Email:    req.Email,
		Name:     req.Name,
		OtpCode:  codeRequest,
		IsActive: true,
	}

	err = _i.Repo.CreateOtp(&otpReq)
	if err != nil {
		return err
	}

	err = _i.SendRegistrationOtp(*req.Name, req.Email, codeRequest)
	if err != nil {
		return err
	}

	return nil
}

func (_i *usersService) OtpValidation(req request.UserOtpValidation) (err error) {
	_i.Log.Info().Interface("data", req).Msg("")

	var otp *entity.OneTimePasswords
	if req.Email == nil {
		otp, err = _i.Repo.FindOtpByIdentity(*req.Username, req.OtpCode)
		if err != nil {
			return fmt.Errorf("OTP is not valid")
		}
	} else {
		otp, err = _i.Repo.FindOtpByEmail(*req.Email, req.OtpCode)
		if err != nil {
			return fmt.Errorf("OTP is not valid")
		}
	}

	if otp != nil {
		if otp.ValidUntil.Before(time.Now()) {
			return fmt.Errorf("OTP has expired")
		}

		return nil
	} else {
		return fmt.Errorf("OTP is not valid")
	}
}

func (_i *usersService) EmailValidationPreLogin(clientId *uuid.UUID, req request.UserEmailValidationRequest) (msgResponse *string, err error) {
	_i.Log.Info().Interface("data", req).Msg("")

	var loginResponse *gocloak.JWT
	loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password)

	if loginResponse == nil || err != nil {
		return nil, fmt.Errorf("username / password incorrect")
	}

	findUser, err := _i.Repo.FindByUsername(clientId, *req.Username)
	if findUser == nil || err != nil {
		return nil, fmt.Errorf("username / password incorrect")
	}

	_i.Log.Info().Interface("data user", findUser).Msg("")

	if *findUser.IsEmailUpdated != true {
		message := "Continue to setup email"
		msgResponse = &message
	} else {
		codeRequest, err := utilSvc.GenerateNumericCode(6)
		if err != nil {
			return nil, err
		}
		otpReq := entity.OneTimePasswords{
			Email:    findUser.Email,
			Identity: &findUser.Username,
			OtpCode:  codeRequest,
			IsActive: true,
		}

		err = _i.Repo.CreateOtp(&otpReq)
		if err != nil {
			return nil, err
		}

		err = _i.SendLoginOtp(findUser.Fullname, findUser.Email, codeRequest)
		if err != nil {
			return nil, err
		} else {
			msg := "Email is valid and OTP has been sent"
			msgResponse = &msg
		}
	}

	return msgResponse, nil
}

func (_i *usersService) SetupEmail(clientId *uuid.UUID, req request.UserEmailValidationRequest) (msgResponse *string, err error) {
	_i.Log.Info().Interface("data", req).Msg("")

	var loginResponse *gocloak.JWT
	loginResponse, err = _i.Keycloak.Login(*req.Username, *req.Password)

	if loginResponse == nil || err != nil {
		return nil, fmt.Errorf("username / password incorrect")
	}

	_i.Log.Info().Interface("findUser", "").Msg("")
	findUser, err := _i.Repo.FindByUsername(clientId, *req.Username)

	_i.Log.Info().Interface("findUser", findUser).Msg("")
	if findUser == nil || err != nil {
		return nil, fmt.Errorf("username / password incorrect")
	}

	isTrue := true
	if findUser.Email == *req.OldEmail {
		findUser.Email = *req.NewEmail
		findUser.IsEmailUpdated = &isTrue
		_i.Log.Info().Interface("Update", "").Msg("")
		err = _i.Repo.Update(clientId, findUser.ID, findUser)
		_i.Log.Info().Interface("Update", err).Msg("")
		if err != nil {
			return nil, err
		}

		codeRequest, err := utilSvc.GenerateNumericCode(6)
		if err != nil {
			return nil, err
		}
		otpReq := entity.OneTimePasswords{
			Email:    findUser.Email,
			Identity: &findUser.Username,
			OtpCode:  codeRequest,
			IsActive: true,
		}

		err = _i.Repo.CreateOtp(&otpReq)
		if err != nil {
			return nil, err
		}

		err = _i.SendLoginOtp(findUser.Fullname, findUser.Email, codeRequest)
		if err != nil {
			return nil, err
		} else {
			msg := "Email is valid and OTP has been sent"
			msgResponse = &msg
		}
	} else {
		return nil, fmt.Errorf("the old email is not same")
	}

	return msgResponse, nil
}

func ParseJWTToken(token string) (map[string]interface{}, error) {
	// Pisahkan JWT menjadi 3 bagian: header, payload, dan signature
	parts := strings.Split(token, ".")
	if len(parts) != 3 {
		return nil, fmt.Errorf("Invalid JWT token")
	}

	// Decode bagian payload (index ke-1)
	payloadData, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil {
		return nil, fmt.Errorf("Failed to decode payload: %v", err)
	}

	// Ubah payload menjadi map[string]interface{}
	var payload map[string]interface{}
	if err := json.Unmarshal(payloadData, &payload); err != nil {
		return nil, fmt.Errorf("Failed to parse payload JSON: %v", err)
	}

	return payload, nil
}

func (_i *usersService) SendLoginOtp(name string, email string, otp string) error {
	subject := "[HUMAS POLRI] Permintaan OTP"
	htmlBody := fmt.Sprintf("<p>Hai %s !</p><p>Berikut kode OTP yang digunakan untuk Login.</p>", name)
	htmlBody += fmt.Sprintf("<p style='padding: 10px 50px; background: #eef2f6; border-radius: 8px; max-width: 300px; text-align: center'><b>%s</b></p>", otp)
	htmlBody += "<p style='padding-top: 10px;'>Kode diatas hanya berlaku selama 10 menit. Harap segera masukkan kode tersebut pada aplikasi HUMAS POLRI.</p>"
	htmlBody += "<p style='padding-top: 10px; padding-bottom: 10px'>Demi menjaga kerahasiaan data kamu, mohon jangan membagikan kode OTP ke siapapun.</p>"
	err := _i.Smtp.SendEmail(subject, email, name, htmlBody)

	return err
}

func (_i *usersService) SendRegistrationOtp(name string, email string, otp string) error {
	subject := "[HUMAS POLRI] Permintaan OTP"
	htmlBody := fmt.Sprintf("<p>Hai %s !</p><p>Berikut kode OTP yang digunakan untuk Verifikasi Registrasi.</p>", name)
	htmlBody += fmt.Sprintf("<p style='padding: 10px 50px; background: #eef2f6; border-radius: 8px; max-width: 300px; text-align: center'><b>%s</b></p>", otp)
	htmlBody += "<p style='padding-top: 10px;'>Kode diatas hanya berlaku selama 10 menit. Harap segera masukkan kode tersebut pada aplikasi HUMAS POLRI.</p>"
	htmlBody += "<p style='padding-top: 10px; padding-bottom: 10px'>Demi menjaga kerahasiaan data kamu, mohon jangan membagikan kode OTP ke siapapun.</p>"
	err := _i.Smtp.SendEmail(subject, email, name, htmlBody)

	return err
}
