package handler

import (
	"github.com/Paca-AI/api/internal/apierr"
	sprintdom "github.com/Paca-AI/api/internal/domain/sprint"
	"github.com/Paca-AI/api/internal/transport/http/dto"
	"github.com/Paca-AI/api/internal/transport/http/middleware"
	"github.com/Paca-AI/api/internal/transport/http/presenter"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

// SprintHandler handles sprint management endpoints.
type SprintHandler struct {
	svc           sprintdom.SprintService
	viewSvc       sprintdom.ViewService
	taskTypeSvc   taskTypeLister
	taskStatusSvc taskStatusLister
}

// SprintHandlerOption customizes optional sprint-handler dependencies.
type SprintHandlerOption func(*SprintHandler)

// WithSprintDefaultTaskTypes enables sprint default-view seeding with explicit task-type filters.
func WithSprintDefaultTaskTypes(taskTypeSvc taskTypeLister) SprintHandlerOption {
	return func(h *SprintHandler) {
		h.taskTypeSvc = taskTypeSvc
	}
}

// WithSprintDefaultTaskStatuses enables sprint default-view seeding with backlog status collapsed columns.
func WithSprintDefaultTaskStatuses(taskStatusSvc taskStatusLister) SprintHandlerOption {
	return func(h *SprintHandler) {
		h.taskStatusSvc = taskStatusSvc
	}
}

// NewSprintHandler returns a SprintHandler wired to the sprint and view services.
func NewSprintHandler(svc sprintdom.SprintService, viewSvc sprintdom.ViewService, opts ...SprintHandlerOption) *SprintHandler {
	h := &SprintHandler{svc: svc, viewSvc: viewSvc}
	for _, opt := range opts {
		if opt != nil {
			opt(h)
		}
	}
	return h
}

// ListSprints handles GET /projects/:projectId/sprints.
func (h *SprintHandler) ListSprints(c *gin.Context) {
	projectID, err := parseProjectID(c)
	if err != nil {
		presenter.Error(c, err)
		return
	}
	sprints, err := h.svc.ListSprints(c.Request.Context(), projectID)
	if err != nil {
		presenter.Error(c, err)
		return
	}
	resp := make([]dto.SprintResponse, 0, len(sprints))
	for _, s := range sprints {
		resp = append(resp, dto.SprintFromEntity(s))
	}
	presenter.OK(c, gin.H{"items": resp})
}

// CreateSprint handles POST /projects/:projectId/sprints.
func (h *SprintHandler) CreateSprint(c *gin.Context) {
	projectID, err := parseProjectID(c)
	if err != nil {
		presenter.Error(c, err)
		return
	}

	var req dto.CreateSprintRequest
	if !middleware.BindJSON(c, &req) {
		return
	}

	s, err := h.svc.CreateSprint(c.Request.Context(), sprintdom.CreateSprintInput{
		ProjectID: projectID,
		Name:      req.Name,
		StartDate: req.StartDate,
		EndDate:   req.EndDate,
		Goal:      req.Goal,
		Status:    req.Status,
	})
	if err != nil {
		presenter.Error(c, err)
		return
	}

	// Seed default views for every new sprint.
	ctx := c.Request.Context()
	taskTypes, loadErr := loadTaskTypes(ctx, h.taskTypeSvc, s.ProjectID)
	if loadErr != nil {
		c.Error(loadErr) //nolint:errcheck
	}
	statuses, statusLoadErr := loadTaskStatuses(ctx, h.taskStatusSvc, s.ProjectID)
	if statusLoadErr != nil {
		c.Error(statusLoadErr) //nolint:errcheck
	}
	for _, input := range defaultSprintViewInputs(s.ProjectID, s.ID, taskTypes, statuses) {
		if _, err := h.viewSvc.CreateView(ctx, input); err != nil {
			// Non-fatal: the sprint was created; log and continue.
			c.Error(err) //nolint:errcheck
		}
	}

	presenter.Created(c, dto.SprintFromEntity(s))
}

// UpdateSprint handles PATCH /projects/:projectId/sprints/:sprintId.
func (h *SprintHandler) UpdateSprint(c *gin.Context) {
	projectID, err := parseProjectID(c)
	if err != nil {
		presenter.Error(c, err)
		return
	}
	sprintID, err := parseSprintID(c)
	if err != nil {
		presenter.Error(c, err)
		return
	}

	var req dto.UpdateSprintRequest
	if !middleware.BindJSON(c, &req) {
		return
	}

	s, err := h.svc.UpdateSprint(c.Request.Context(), projectID, sprintID, sprintdom.UpdateSprintInput{
		Name:      req.Name,
		StartDate: req.StartDate,
		EndDate:   req.EndDate,
		Goal:      req.Goal,
		Status:    req.Status,
	})
	if err != nil {
		presenter.Error(c, err)
		return
	}
	presenter.OK(c, dto.SprintFromEntity(s))
}

// DeleteSprint handles DELETE /projects/:projectId/sprints/:sprintId.
func (h *SprintHandler) DeleteSprint(c *gin.Context) {
	projectID, err := parseProjectID(c)
	if err != nil {
		presenter.Error(c, err)
		return
	}
	sprintID, err := parseSprintID(c)
	if err != nil {
		presenter.Error(c, err)
		return
	}
	if err := h.svc.DeleteSprint(c.Request.Context(), projectID, sprintID); err != nil {
		presenter.Error(c, err)
		return
	}
	presenter.OK(c, gin.H{"message": "sprint deleted"})
}

// CompleteSprint handles POST /projects/:projectId/sprints/:sprintId/complete.
// It bulk-moves all non-done tasks to the requested destination sprint (or the
// backlog when move_to_sprint_id is absent/null) and marks the sprint completed.
func (h *SprintHandler) CompleteSprint(c *gin.Context) {
	projectID, err := parseProjectID(c)
	if err != nil {
		presenter.Error(c, err)
		return
	}
	sprintID, err := parseSprintID(c)
	if err != nil {
		presenter.Error(c, err)
		return
	}

	var req dto.CompleteSprintRequest
	if !middleware.BindJSON(c, &req) {
		return
	}

	s, err := h.svc.CompleteSprint(c.Request.Context(), projectID, sprintID, sprintdom.CompleteSprintInput{
		MoveToSprintID: req.MoveToSprintID,
	})
	if err != nil {
		presenter.Error(c, err)
		return
	}
	presenter.OK(c, dto.SprintFromEntity(s))
}

// parseSprintID extracts and validates the :sprintId path parameter.
func parseSprintID(c *gin.Context) (uuid.UUID, error) {
	id, err := uuid.Parse(c.Param("sprintId"))
	if err != nil {
		return uuid.Nil, apierr.New(apierr.CodeBadRequest, "invalid sprint id")
	}
	return id, nil
}

// GetSprint handles GET /projects/:projectId/sprints/:sprintId.
func (h *SprintHandler) GetSprint(c *gin.Context) {
	projectID, err := parseProjectID(c)
	if err != nil {
		presenter.Error(c, err)
		return
	}
	sprintID, err := parseSprintID(c)
	if err != nil {
		presenter.Error(c, err)
		return
	}
	s, err := h.svc.GetSprint(c.Request.Context(), projectID, sprintID)
	if err != nil {
		presenter.Error(c, err)
		return
	}
	presenter.OK(c, dto.SprintFromEntity(s))
}
