package handlers

import (
	"context"
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/timetracker/backend/internal/api/dto"
	"github.com/timetracker/backend/internal/api/middleware"
	"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"
)

// CustomerHandler handles customer-related API endpoints
type CustomerHandler struct{}

// NewCustomerHandler creates a new CustomerHandler
func NewCustomerHandler() *CustomerHandler {
	return &CustomerHandler{}
}

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

// GetCustomerByID handles GET /customers/:id
//
//	@Summary		Get customer by ID
//	@Description	Get a customer by its ID
//	@Tags			customers
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Param			id	path		string	true	"Customer ID"
//	@Success		200	{object}	utils.Response{data=dto.CustomerDto}
//	@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			/customers/{id} [get]
func (h *CustomerHandler) GetCustomerByID(c *gin.Context) {
	utils.HandleGetByID(c, models.GetCustomerByID, convertCustomerToDTO, "customer")
}

// GetCustomersByCompanyID handles GET /customers/company/:companyId
//
//	@Summary		Get customers by company ID
//	@Description	Get a list of customers for a specific company
//	@Tags			customers
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Param			companyId	path		int	true	"Company ID"
//	@Success		200			{object}	utils.Response{data=[]dto.CustomerDto}
//	@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			/customers/company/{companyId} [get]
func (h *CustomerHandler) GetCustomersByCompanyID(c *gin.Context) {
	// Parse company ID from URL
	companyIDStr := c.Param("companyId")
	companyID, err := parseCompanyID(companyIDStr)
	if err != nil {
		responses.BadRequestResponse(c, "Invalid company ID format")
		return
	}

	// Create a wrapper function that takes a ULID but calls the original function with an int
	getByCompanyIDFn := func(ctx context.Context, _ types.ULID) ([]models.Customer, error) {
		return models.GetCustomersByCompanyID(ctx, companyID)
	}

	// Get customers from the database and convert to DTOs
	customers, err := getByCompanyIDFn(c.Request.Context(), types.ULID{})
	if err != nil {
		responses.InternalErrorResponse(c, "Error retrieving customers: "+err.Error())
		return
	}

	// Convert to DTOs
	customerDTOs := utils.ConvertToDTO(customers, convertCustomerToDTO)

	responses.SuccessResponse(c, http.StatusOK, customerDTOs)
}

// CreateCustomer handles POST /customers
//
//	@Summary		Create a new customer
//	@Description	Create a new customer
//	@Tags			customers
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Param			customer	body		dto.CustomerCreateDto	true	"Customer data"
//	@Success		201			{object}	utils.Response{data=dto.CustomerDto}
//	@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			/customers [post]
func (h *CustomerHandler) CreateCustomer(c *gin.Context) {
	// We need to use a custom wrapper for CreateCustomer because we need to get the user ID from the context
	userID, err := middleware.GetUserIDFromContext(c)
	if err != nil {
		responses.UnauthorizedResponse(c, "User not authenticated")
		return
	}

	// Use a closure to capture the userID
	createFn := func(ctx context.Context, createDTO dto.CustomerCreateDto) (*models.Customer, error) {
		return createCustomerWrapper(ctx, createDTO, userID)
	}

	utils.HandleCreate(c, createFn, convertCustomerToDTO, "customer")
}

// UpdateCustomer handles PUT /customers/:id
//
//	@Summary		Update a customer
//	@Description	Update an existing customer
//	@Tags			customers
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Param			id			path		string					true	"Customer ID"
//	@Param			customer	body		dto.CustomerUpdateDto	true	"Customer data"
//	@Success		200			{object}	utils.Response{data=dto.CustomerDto}
//	@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			/customers/{id} [put]
func (h *CustomerHandler) UpdateCustomer(c *gin.Context) {
	utils.HandleUpdate(c, models.UpdateCustomer, convertCustomerToDTO, prepareCustomerUpdate, "customer")
}

// DeleteCustomer handles DELETE /customers/:id
//
//	@Summary		Delete a customer
//	@Description	Delete a customer by its ID
//	@Tags			customers
//	@Accept			json
//	@Produce		json
//	@Security		BearerAuth
//	@Param			id	path		string	true	"Customer 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			/customers/{id} [delete]
func (h *CustomerHandler) DeleteCustomer(c *gin.Context) {
	utils.HandleDelete(c, models.DeleteCustomer, "customer")
}

// Helper functions for DTO conversion

func convertCustomerToDTO(customer *models.Customer) dto.CustomerDto {
	var companyID *string
	if customer.CompanyID != nil {
		s := customer.CompanyID.String()
		companyID = &s
	}
	return dto.CustomerDto{
		ID:        customer.ID.String(),
		CreatedAt: customer.CreatedAt,
		UpdatedAt: customer.UpdatedAt,
		Name:      customer.Name,
		CompanyID: companyID,
	}
}

func convertCreateCustomerDTOToModel(dto dto.CustomerCreateDto) (models.CustomerCreate, error) {
	var companyID *types.ULID
	if dto.CompanyID != nil {
		wrapper, err := types.ULIDFromString(*dto.CompanyID) // Ignoring error, validation happens in the model
		if err != nil {
			return models.CustomerCreate{}, fmt.Errorf("invalid company ID: %w", err)
		}
		companyID = &wrapper
	}
	create := models.CustomerCreate{
		Name:      dto.Name,
		CompanyID: companyID,
	}
	return create, nil
}

func convertUpdateCustomerDTOToModel(dto dto.CustomerUpdateDto) (models.CustomerUpdate, error) {
	id, err := types.ULIDFromString(dto.ID)
	if err != nil {
		return models.CustomerUpdate{}, fmt.Errorf("invalid customer ID: %w", err)
	}

	update := models.CustomerUpdate{
		ID: id,
	}

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

	if dto.CompanyID.Valid {
		companyID, err := types.ULIDFromString(*dto.CompanyID.Value)
		if err != nil {
			return models.CustomerUpdate{}, fmt.Errorf("invalid company ID: %w", err)
		}
		update.CompanyID = &companyID
	} else {
		update.CompanyID = nil
	}

	return update, nil
}

// Helper function to parse company ID from string
func parseCompanyID(idStr string) (int, error) {
	var id int
	_, err := fmt.Sscanf(idStr, "%d", &id)
	return id, err
}

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

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

	// Set ID from URL
	customerUpdateDTO.ID = id.String()

	// Convert DTO to model
	customerUpdate, err := convertUpdateCustomerDTOToModel(customerUpdateDTO)
	if err != nil {
		responses.BadRequestResponse(c, "Invalid request body: "+err.Error())
		return models.CustomerUpdate{}, err
	}

	return customerUpdate, nil
}

// createCustomerWrapper is a wrapper function for models.CreateCustomer that takes a DTO as input
func createCustomerWrapper(ctx context.Context, createDTO dto.CustomerCreateDto, userID types.ULID) (*models.Customer, error) {
	// Convert DTO to model
	customerCreate, err := convertCreateCustomerDTOToModel(createDTO)
	if err != nil {
		return nil, err
	}

	// Set the owner user ID
	customerCreate.OwnerUserID = &userID

	// Call the original function
	return models.CreateCustomer(ctx, customerCreate)
}
