package handlers

import (
	"errors"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/tdvorak/seen/backend/internal/domain"
	"github.com/tdvorak/seen/backend/internal/services/auth"
	"github.com/tdvorak/seen/backend/internal/services/download"
	"github.com/tdvorak/seen/backend/pkg/httpx"
)

type DownloadHandler struct {
	service     *download.Service
	authService *auth.Service
}

func NewDownloadHandler(service *download.Service, authService *auth.Service) *DownloadHandler {
	return &DownloadHandler{
		service:     service,
		authService: authService,
	}
}

type createDownloadRequest struct {
	SourceType string `json:"sourceType"`
	Source     string `json:"source"`
	Title      string `json:"title"`
}

func (h *DownloadHandler) Create(c *gin.Context) {
	user, ok := h.resolveUser(c)
	if !ok {
		return
	}

	var request createDownloadRequest
	if err := c.ShouldBindJSON(&request); err != nil {
		httpx.JSONError(c, http.StatusBadRequest, "invalid request body")
		return
	}

	job, err := h.service.Create(c.Request.Context(), user.ID, download.CreateInput{
		SourceType: request.SourceType,
		Source:     request.Source,
		Title:      request.Title,
	})
	if err != nil {
		switch {
		case errors.Is(err, download.ErrInvalidInput):
			httpx.JSONError(c, http.StatusBadRequest, err.Error())
		default:
			httpx.JSONError(c, http.StatusInternalServerError, "failed to create download job")
		}
		return
	}

	httpx.JSON(c, http.StatusCreated, job)
}

func (h *DownloadHandler) List(c *gin.Context) {
	user, ok := h.resolveUser(c)
	if !ok {
		return
	}

	items, err := h.service.List(c.Request.Context(), user.ID, download.ListParams{
		Status: c.Query("status"),
		Limit:  parseIntSafe(c.Query("limit"), 20),
		Offset: parseIntSafe(c.Query("offset"), 0),
	})
	if err != nil {
		switch {
		case errors.Is(err, download.ErrInvalidInput):
			httpx.JSONError(c, http.StatusBadRequest, err.Error())
		default:
			httpx.JSONError(c, http.StatusInternalServerError, "failed to list download jobs")
		}
		return
	}

	httpx.JSON(c, http.StatusOK, items)
}

func (h *DownloadHandler) Cancel(c *gin.Context) {
	user, ok := h.resolveUser(c)
	if !ok {
		return
	}

	jobID := c.Param("id")
	job, err := h.service.Cancel(c.Request.Context(), user.ID, jobID)
	if err != nil {
		switch {
		case errors.Is(err, download.ErrInvalidInput):
			httpx.JSONError(c, http.StatusBadRequest, err.Error())
		case errors.Is(err, download.ErrNotFound):
			httpx.JSONError(c, http.StatusNotFound, err.Error())
		default:
			httpx.JSONError(c, http.StatusInternalServerError, "failed to cancel download job")
		}
		return
	}

	httpx.JSON(c, http.StatusOK, job)
}

func (h *DownloadHandler) Events(c *gin.Context) {
	user, ok := h.resolveUser(c)
	if !ok {
		return
	}

	jobID := c.Param("id")
	items, err := h.service.Events(c.Request.Context(), user.ID, jobID, download.EventParams{
		After: c.Query("after"),
		Limit: parseIntSafe(c.Query("limit"), 100),
	})
	if err != nil {
		switch {
		case errors.Is(err, download.ErrInvalidInput):
			httpx.JSONError(c, http.StatusBadRequest, err.Error())
		case errors.Is(err, download.ErrNotFound):
			httpx.JSONError(c, http.StatusNotFound, err.Error())
		default:
			httpx.JSONError(c, http.StatusInternalServerError, "failed to list download events")
		}
		return
	}

	httpx.JSON(c, http.StatusOK, items)
}

func (h *DownloadHandler) resolveUser(c *gin.Context) (*domain.User, bool) {
	accessToken, ok := bearerToken(c.GetHeader("Authorization"))
	if !ok {
		httpx.JSONError(c, http.StatusUnauthorized, "missing bearer token")
		return nil, false
	}

	user, err := h.authService.UserFromAccessToken(c.Request.Context(), accessToken)
	if err != nil {
		switch {
		case errors.Is(err, auth.ErrInvalidToken):
			httpx.JSONError(c, http.StatusUnauthorized, err.Error())
		default:
			httpx.JSONError(c, http.StatusInternalServerError, "failed to resolve user")
		}
		return nil, false
	}

	return user, true
}

func parseIntSafe(raw string, fallback int) int {
	if raw == "" {
		return fallback
	}

	parsed, err := strconv.Atoi(raw)
	if err != nil {
		return fallback
	}

	return parsed
}
