package handlers

import (
	"context"
	"fmt"

	"github.com/gin-gonic/gin"
	"github.com/timetracker/backend/internal/api/dto"
	"github.com/timetracker/backend/internal/api/responses"
	"github.com/timetracker/backend/internal/api/utils"
	"github.com/timetracker/backend/internal/models"
	"github.com/timetracker/backend/internal/types"
)

// ProjectHandler handles project-related API endpoints
type ProjectHandler struct{}

// NewProjectHandler creates a new ProjectHandler
func NewProjectHandler() *ProjectHandler {
	return &ProjectHandler{}
}

// GetProjects handles GET /projects
//
//	@Summary		Get all projects
//	@Description	Get a list of all projects
//	@Tags			projects
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Success		200	{object}	utils.Response{data=[]dto.ProjectDto}
//	@Failure		401	{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500	{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/projects [get]
func (h *ProjectHandler) GetProjects(c *gin.Context) {
	utils.HandleGetAll(c, models.GetAllProjects, convertProjectToDTO, "projects")
}

// GetProjectsWithCustomers handles GET /projects/with-customers
//
//	@Summary		Get all projects with customer information
//	@Description	Get a list of all projects with their associated customer information
//	@Tags			projects
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Success		200	{object}	utils.Response{data=[]dto.ProjectDto}
//	@Failure		401	{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500	{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/projects/with-customers [get]
func (h *ProjectHandler) GetProjectsWithCustomers(c *gin.Context) {
	utils.HandleGetAll(c, models.GetAllProjectsWithCustomers, convertProjectToDTO, "projects with customers")
}

// GetProjectByID handles GET /projects/:id
//
//	@Summary		Get project by ID
//	@Description	Get a project by its ID
//	@Tags			projects
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Param			id	path		string	true	"Project ID"
//	@Success		200	{object}	utils.Response{data=dto.ProjectDto}
//	@Failure		400	{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		401	{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		404	{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500	{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/projects/{id} [get]
func (h *ProjectHandler) GetProjectByID(c *gin.Context) {
	utils.HandleGetByID(c, models.GetProjectByID, convertProjectToDTO, "project")
}

// GetProjectsByCustomerID handles GET /projects/customer/:customerId
//
//	@Summary		Get projects by customer ID
//	@Description	Get a list of projects for a specific customer
//	@Tags			projects
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Param			customerId	path		string	true	"Customer ID"
//	@Success		200			{object}	utils.Response{data=[]dto.ProjectDto}
//	@Failure		400			{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		401			{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500			{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/projects/customer/{customerId} [get]
func (h *ProjectHandler) GetProjectsByCustomerID(c *gin.Context) {
	utils.HandleGetByFilter(c, models.GetProjectsByCustomerID, convertProjectToDTO, "projects", "customerId")
}

// CreateProject handles POST /projects
//
//	@Summary		Create a new project
//	@Description	Create a new project
//	@Tags			projects
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Param			project	body		dto.ProjectCreateDto	true	"Project data"
//	@Success		201		{object}	utils.Response{data=dto.ProjectDto}
//	@Failure		400		{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		401		{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500		{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/projects [post]
func (h *ProjectHandler) CreateProject(c *gin.Context) {
	utils.HandleCreate(c, createProjectWrapper, convertProjectToDTO, "project")
}

// UpdateProject handles PUT /projects/:id
//
//	@Summary		Update a project
//	@Description	Update an existing project
//	@Tags			projects
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Param			id		path		string				true	"Project ID"
//	@Param			project	body		dto.ProjectUpdateDto	true	"Project data"
//	@Success		200		{object}	utils.Response{data=dto.ProjectDto}
//	@Failure		400		{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		401		{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		404		{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500		{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/projects/{id} [put]
func (h *ProjectHandler) UpdateProject(c *gin.Context) {
	utils.HandleUpdate(c, models.UpdateProject, convertProjectToDTO, prepareProjectUpdate, "project")
}

// DeleteProject handles DELETE /projects/:id
//
//	@Summary		Delete a project
//	@Description	Delete a project by its ID
//	@Tags			projects
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Param			id	path		string	true	"Project ID"
//	@Success		204	{object}	utils.Response
//	@Failure		400	{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		401	{object}	utils.Response{error=utils.ErrorInfo}
//	@Failure		500	{object}	utils.Response{error=utils.ErrorInfo}
//	@Router			/projects/{id} [delete]
func (h *ProjectHandler) DeleteProject(c *gin.Context) {
	utils.HandleDelete(c, models.DeleteProject, "project")
}

// Helper functions for DTO conversion

func convertProjectToDTO(project *models.Project) dto.ProjectDto {
	var customerIdPtr *string
	if project.CustomerID != nil {
		customerIdStr := project.CustomerID.String()
		customerIdPtr = &customerIdStr
	}
	return dto.ProjectDto{
		ID:         project.ID.String(),
		CreatedAt:  project.CreatedAt,
		UpdatedAt:  project.UpdatedAt,
		Name:       project.Name,
		CustomerID: customerIdPtr,
	}
}

func convertCreateProjectDTOToModel(dto dto.ProjectCreateDto) (models.ProjectCreate, error) {
	create := models.ProjectCreate{Name: dto.Name}
	// Convert CustomerID from int to ULID (this is a simplification, adjust as needed)
	if dto.CustomerID != nil {
		customerID, err := types.ULIDFromString(*dto.CustomerID)
		if err != nil {
			return models.ProjectCreate{}, fmt.Errorf("invalid customer ID: %w", err)
		}
		create.CustomerID = &customerID
	}
	return create, nil
}

func convertUpdateProjectDTOToModel(dto dto.ProjectUpdateDto, id types.ULID) (models.ProjectUpdate, error) {
	update := models.ProjectUpdate{
		ID: id,
	}

	if dto.Name != nil {
		update.Name = dto.Name
	}

	if dto.CustomerID.Valid {
		if dto.CustomerID.Value == nil {
			update.CustomerID = types.Null[types.ULID]()
		} else {
			customerID, err := types.ULIDFromString(*dto.CustomerID.Value)
			if err != nil {
				return models.ProjectUpdate{}, fmt.Errorf("invalid customer ID: %w", err)
			}
			update.CustomerID = types.NewNullable(customerID)
		}
	}

	return update, nil
}

// prepareProjectUpdate prepares the project update object by parsing the ID, binding the JSON, and converting the DTO to a model
func prepareProjectUpdate(c *gin.Context) (models.ProjectUpdate, error) {
	// Parse ID from URL
	id, err := utils.ParseID(c, "id")
	if err != nil {
		responses.BadRequestResponse(c, "Invalid project ID format")
		return models.ProjectUpdate{}, err
	}

	// Parse request body
	var projectUpdateDTO dto.ProjectUpdateDto
	if err := utils.BindJSON(c, &projectUpdateDTO); err != nil {
		responses.BadRequestResponse(c, err.Error())
		return models.ProjectUpdate{}, err
	}

	// Convert DTO to model
	projectUpdate, err := convertUpdateProjectDTOToModel(projectUpdateDTO, id)
	if err != nil {
		responses.BadRequestResponse(c, err.Error())
		return models.ProjectUpdate{}, err
	}

	return projectUpdate, nil
}

// createProjectWrapper is a wrapper function for models.CreateProject that takes a DTO as input
func createProjectWrapper(ctx context.Context, createDTO dto.ProjectCreateDto) (*models.Project, error) {
	// Convert DTO to model
	projectCreate, err := convertCreateProjectDTOToModel(createDTO)
	if err != nil {
		return nil, err
	}

	// Call the original function
	return models.CreateProject(ctx, projectCreate)
}
