package services

import (
	"actatempus_backend/internal/application/services/dto"
	mappers "actatempus_backend/internal/application/services/mapper"
	"actatempus_backend/internal/domain/app_error"
	"actatempus_backend/internal/domain/entities"
	"actatempus_backend/internal/domain/repository"
	"actatempus_backend/internal/infrastructure/data"
	"net/http"

	E "github.com/IBM/fp-go/either"
	F "github.com/IBM/fp-go/function"
	"github.com/gin-gonic/gin"
)

// AuthService handles authentication-related HTTP requests.
type AuthService struct {
	authRepository repository.AuthRepository
	userRepository repository.UserRepository
}

// NewAuthService creates a new instance of AuthService.
func NewAuthService(authRepo repository.AuthRepository,
	userRepository repository.UserRepository) *AuthService {
	return &AuthService{
		authRepository: authRepo,
		userRepository: userRepository,
	}
}

// RegisterRoutes registers the authentication-related routes.
func (s *AuthService) RegisterRoutes(router *gin.RouterGroup) {
	router.POST("/login", s.Login)
	router.POST("/validate", s.Validate)
	router.POST("/logout", s.Logout)
}

// Login handles user login and token generation.
func (s *AuthService) Login(c *gin.Context) {
	var loginRequest dto.LoginRequestDTO
	if err := c.ShouldBindJSON(&loginRequest); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	F.Pipe4(
		loginRequest.Email,
		s.userRepository.FindByEmail(c.Request.Context()),
		E.Chain[error](
			validatePassword(loginRequest.Password),
		),
		E.Chain(func(user entities.User) E.Either[error, dto.TokenResponseDTO] {
			return F.Pipe3(
				user.ID,
				s.authRepository.GenerateToken(c.Request.Context()),
				E.Map[error](setSessionTokenCookie(c, user)),
				E.Map[error](mappers.ToTokenDTO(user)),
			)
		}),
		E.Fold(
			HandleError(c),
			HandleSuccess[dto.TokenResponseDTO](c, http.StatusOK),
		),
	)
}

// Validate handles token validation.
func (s *AuthService) Validate(c *gin.Context) {
	F.Pipe4(
		ReadSessionToken(c),
		E.FromOption[string, error](
			F.Constant[error](app_error.NewUnauthorizedError("No session token found"))),
		E.Chain(
			s.authRepository.ValidateToken(c.Request.Context()),
		),
		E.Map[error](func(userID string) gin.H {
			return gin.H{"message": "Token validated", "user_id": userID}
		}),
		E.Fold(
			HandleError(c),
			HandleSuccess[gin.H](c, http.StatusOK),
		),
	)
}

// Logout and token revocation.
func (s *AuthService) Logout(c *gin.Context) {
	F.Pipe4(
		ReadSessionToken(c),
		E.FromOption[string, error](
			F.Constant[error](app_error.NewUnauthorizedError("No session token found"))),
		E.Chain(
			s.authRepository.RevokeToken(c.Request.Context()),
		),
		E.Map[error](func(userID string) gin.H {
			deleteSessionTokenCookie(c)
			return gin.H{"message": "Token revoked", "user_id": userID}
		}),
		E.Fold(
			HandleError(c),
			HandleSuccess[gin.H](c, http.StatusOK),
		),
	)
}

func setSessionTokenCookie(c *gin.Context, user entities.User) func(token string) string {
	return func(token string) string {
		c.SetCookie("session_token", token, 3600, "/", "", false, true)
		c.SetCookie("user_id", user.ID, 3600, "/", "", false, true)
		return token
	}
}

func deleteSessionTokenCookie(c *gin.Context) {
	c.SetCookie("session_token", "", -1, "/", "", false, true)
	c.SetCookie("user_id", "", -1, "/", "", false, true)
}

func validatePassword(password string) func(user entities.User) E.Either[error, entities.User] {
	return func(user entities.User) E.Either[error, entities.User] {
		hashedPassword := data.GenerateSecureHash(password)
		if user.Password != hashedPassword {
			return E.Left[entities.User, error](app_error.NewAuthError("Invalid password"))
		}
		return E.Right[error](user)
	}
}
