package services

import (
	"actatempus_backend/internal/application/services/dto"
	mappers "actatempus_backend/internal/application/services/mapper"
	"actatempus_backend/internal/domain/repository"
	"net/http"

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

// TimeEntryService handles time entry-related HTTP requests.
type TimeEntryService struct {
	authRepository repository.AuthRepository
	repository     repository.TimeEntryRepository
}

// NewTimeEntryService creates a new instance of TimeEntryService.
func NewTimeEntryService(authRepo repository.AuthRepository, repo repository.TimeEntryRepository) *TimeEntryService {
	return &TimeEntryService{authRepository: authRepo, repository: repo}
}

// RegisterRoutes registers the time entry-related routes with Gin.
func (s *TimeEntryService) RegisterRoutes(router *gin.RouterGroup) {
	router.POST("/", s.CreateTimeEntry)
	router.GET("/:id", s.GetTimeEntryByID)
	router.GET("/", s.GetAllTimeEntries)
	router.GET("/user/:userID", s.GetTimeEntriesByUserID)
	router.GET("/project/:projectID", s.GetTimeEntriesByProjectID)
	router.PUT("/:id", s.UpdateTimeEntry)
	router.DELETE("/:id", s.DeleteTimeEntry)
}

// CreateTimeEntry handles the creation of a new time entry.
func (s *TimeEntryService) CreateTimeEntry(c *gin.Context) {
	var timeEntryCreateDTO dto.TimeEntryCreateDTO
	if err := c.ShouldBindJSON(&timeEntryCreateDTO); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	F.Pipe5( // disabled for creating the initial user
		CheckAuth(c, s.authRepository),
		E.Map[error](F.Constant1[string](timeEntryCreateDTO)),
		E.Map[error](mappers.MapCreateDTOToTimeEntry),
		E.Chain(s.repository.Create(c.Request.Context())),
		E.Map[error](mappers.MapTimeEntryToDTO),
		E.Fold(
			HandleError(c),
			HandleSuccess[dto.TimeEntryDTO](c, http.StatusCreated),
		),
	)
}

// GetTimeEntryByID handles fetching a time entry by its ID.
func (s *TimeEntryService) GetTimeEntryByID(c *gin.Context) {
	F.Pipe4(
		CheckAuth(c, s.authRepository),
		E.Map[error](F.Constant1[string](c.Param("id"))),
		E.Chain(s.repository.FindByID(c.Request.Context())),
		E.Map[error](mappers.MapTimeEntryToDTO),
		E.Fold(
			HandleError(c),
			HandleSuccess[dto.TimeEntryDTO](c, http.StatusOK),
		),
	)
}

// GetTimeEntriesByUserID handles fetching time entries by user ID.
func (s *TimeEntryService) GetTimeEntriesByUserID(c *gin.Context) {
	F.Pipe4(
		CheckAuth(c, s.authRepository),
		E.Map[error](F.Constant1[string](c.Param("userID"))),
		E.Chain(s.repository.FindByUserID(c.Request.Context())),
		E.Map[error](A.Map(mappers.MapTimeEntryToDTO)),
		E.Fold(
			HandleError(c),
			HandleSuccess[[]dto.TimeEntryDTO](c, http.StatusOK),
		),
	)
}

// GetTimeEntriesByProjectID handles fetching time entries by project ID.
func (s *TimeEntryService) GetTimeEntriesByProjectID(c *gin.Context) {
	F.Pipe4(
		CheckAuth(c, s.authRepository),
		E.Map[error](F.Constant1[string](c.Param("projectID"))),
		E.Chain(s.repository.FindByProjectID(c.Request.Context())),
		E.Map[error](A.Map(mappers.MapTimeEntryToDTO)),
		E.Fold(
			HandleError(c),
			HandleSuccess[[]dto.TimeEntryDTO](c, http.StatusOK),
		),
	)
}

// GetAllTimeEntries handles fetching all time entries.
func (s *TimeEntryService) GetAllTimeEntries(c *gin.Context) {
	F.Pipe3(
		CheckAuth(c, s.authRepository),
		E.Chain(F.Constant1[string](s.repository.FindAll(c.Request.Context()))),
		E.Map[error](A.Map(mappers.MapTimeEntryToDTO)),
		E.Fold(
			HandleError(c),
			HandleSuccess[[]dto.TimeEntryDTO](c, http.StatusOK),
		),
	)
}

// UpdateTimeEntry handles updating an existing time entry.
func (s *TimeEntryService) UpdateTimeEntry(c *gin.Context) {
	var timeEntryUpdateDTO dto.TimeEntryUpdateDTO

	if err := c.ShouldBindJSON(&timeEntryUpdateDTO); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	F.Pipe5(
		CheckAuth(c, s.authRepository),
		E.Map[error](F.Constant1[string](c.Param("id"))),
		E.Map[error](
			F.Curry2(mappers.MapUpdateDTOToTimeEntry)(timeEntryUpdateDTO)),
		E.Chain(s.repository.Update(c.Request.Context())),
		E.Map[error](mappers.MapTimeEntryToDTO),
		E.Fold(
			HandleError(c),
			HandleSuccess[dto.TimeEntryDTO](c, http.StatusOK),
		),
	)
}

// DeleteTimeEntry handles deleting a time entry by its ID and returns the deleted object.
func (s *TimeEntryService) DeleteTimeEntry(c *gin.Context) {
	F.Pipe4(
		CheckAuth(c, s.authRepository),
		E.Map[error](F.Constant1[string](c.Param("id"))),
		E.Chain(s.repository.Delete(c.Request.Context())),
		E.Map[error](mappers.MapTimeEntryToDTO),
		E.Fold(
			HandleError(c),
			HandleSuccess[dto.TimeEntryDTO](c, http.StatusOK),
		),
	)
}
