package web

import (
	"errors"
	"log/slog"
	"net/http"
	"strings"

	"backend/internal/db/sqlc"
	"backend/templates"

	"github.com/go-chi/chi/v5"
	"github.com/google/uuid"
	"github.com/gorilla/csrf"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgtype"
)

type EtapesDeps struct {
	Queries *sqlc.Queries
}

func buildEtapeTaskCounts(tasks []sqlc.Task) templates.EtapeTaskCounts {
	counts := templates.EtapeTaskCounts{
		All:     len(tasks),
		ByEtape: make(map[uuid.UUID]int),
	}
	for _, task := range tasks {
		if task.EtapeID.Valid {
			counts.ByEtape[uuid.UUID(task.EtapeID.Bytes)]++
			continue
		}
		counts.Unassigned++
	}
	return counts
}

func taskHasEtape(task sqlc.Task, id uuid.UUID) bool {
	return task.EtapeID.Valid && uuid.UUID(task.EtapeID.Bytes) == id
}

func loadTasksTabData(w http.ResponseWriter, r *http.Request, q *sqlc.Queries, tablo sqlc.Tablo) ([]sqlc.Task, []sqlc.Etape, templates.EtapeTaskCounts, templates.EtapeFilter, bool) {
	ctx := r.Context()
	etapes, err := q.ListEtapesByTablo(ctx, tablo.ID)
	if err != nil {
		slog.Default().Error("tasks tab: ListEtapesByTablo failed", "tablo_id", tablo.ID, "err", err)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, false
	}
	if etapes == nil {
		etapes = []sqlc.Etape{}
	}

	allTasks, err := q.ListTasksByTablo(ctx, tablo.ID)
	if err != nil {
		slog.Default().Error("tasks tab: ListTasksByTablo failed", "tablo_id", tablo.ID, "err", err)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, false
	}
	if allTasks == nil {
		allTasks = []sqlc.Task{}
	}

	counts := buildEtapeTaskCounts(allTasks)
	filter := templates.EtapeFilter{Kind: templates.EtapeFilterAll}
	tasks := allTasks
	rawFilter := strings.TrimSpace(r.URL.Query().Get("etape"))
	switch {
	case rawFilter == "":
	case rawFilter == "unassigned":
		filter.Kind = templates.EtapeFilterUnassigned
		tasks = tasks[:0]
		for _, task := range allTasks {
			if !task.EtapeID.Valid {
				tasks = append(tasks, task)
			}
		}
	default:
		etapeID, err := uuid.Parse(rawFilter)
		if err != nil {
			http.NotFound(w, r)
			return nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, false
		}
		if _, err := q.GetEtapeByID(ctx, sqlc.GetEtapeByIDParams{ID: etapeID, TabloID: tablo.ID}); err != nil {
			if errors.Is(err, pgx.ErrNoRows) {
				http.NotFound(w, r)
				return nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, false
			}
			slog.Default().Error("tasks tab: GetEtapeByID failed", "tablo_id", tablo.ID, "etape_id", etapeID, "err", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, false
		}
		filter = templates.EtapeFilter{Kind: templates.EtapeFilterEtape, EtapeID: etapeID}
		tasks = tasks[:0]
		for _, task := range allTasks {
			if taskHasEtape(task, etapeID) {
				tasks = append(tasks, task)
			}
		}
	}

	return tasks, etapes, counts, filter, true
}

func parseOwnedEtapeID(r *http.Request, q *sqlc.Queries, tabloID uuid.UUID, raw string) (pgtype.UUID, bool, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return pgtype.UUID{}, false, nil
	}
	etapeID, err := uuid.Parse(raw)
	if err != nil {
		return pgtype.UUID{}, false, err
	}
	if _, err := q.GetEtapeByID(r.Context(), sqlc.GetEtapeByIDParams{ID: etapeID, TabloID: tabloID}); err != nil {
		return pgtype.UUID{}, false, err
	}
	return pgtype.UUID{Bytes: etapeID, Valid: true}, true, nil
}

func loadOwnedEtape(w http.ResponseWriter, r *http.Request, deps EtapesDeps) (sqlc.Tablo, sqlc.Etape, bool) {
	tablo, _, ok := loadOwnedTablo(w, r, TablosDeps{Queries: deps.Queries})
	if !ok {
		return sqlc.Tablo{}, sqlc.Etape{}, false
	}
	etapeID, err := uuid.Parse(chi.URLParam(r, "etape_id"))
	if err != nil {
		http.NotFound(w, r)
		return sqlc.Tablo{}, sqlc.Etape{}, false
	}
	etape, err := deps.Queries.GetEtapeByID(r.Context(), sqlc.GetEtapeByIDParams{ID: etapeID, TabloID: tablo.ID})
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			http.NotFound(w, r)
			return sqlc.Tablo{}, sqlc.Etape{}, false
		}
		slog.Default().Error("etapes: GetEtapeByID failed", "tablo_id", tablo.ID, "etape_id", etapeID, "err", err)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return sqlc.Tablo{}, sqlc.Etape{}, false
	}
	return tablo, etape, true
}

func renderTasksTab(w http.ResponseWriter, r *http.Request, q *sqlc.Queries, tablo sqlc.Tablo) {
	tasks, etapes, counts, filter, ok := loadTasksTabData(w, r, q, tablo)
	if !ok {
		return
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if r.Header.Get("HX-Request") == "true" {
		w.Header().Set("HX-Retarget", "#tasks-tab")
		w.Header().Set("HX-Reswap", "outerHTML")
	}
	_ = templates.TasksTabFragment(tablo, tasks, etapes, counts, filter, csrf.Token(r)).Render(r.Context(), w)
}

func EtapeNewFormHandler(deps EtapesDeps) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tablo, _, ok := loadOwnedTablo(w, r, TablosDeps{Queries: deps.Queries})
		if !ok {
			return
		}
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		_ = templates.EtapeCreateFormFragment(tablo.ID, templates.EtapeCreateForm{}, templates.EtapeCreateErrors{}, csrf.Token(r)).Render(r.Context(), w)
	}
}

func EtapeEditFormHandler(deps EtapesDeps) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tablo, etape, ok := loadOwnedEtape(w, r, deps)
		if !ok {
			return
		}
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		_ = templates.EtapeEditFormFragment(
			tablo.ID,
			etape,
			templates.EtapeUpdateForm{Title: etape.Title, Description: etape.Description.String},
			templates.EtapeUpdateErrors{},
			csrf.Token(r),
		).Render(r.Context(), w)
	}
}

func EtapeUpdateHandler(deps EtapesDeps) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tablo, etape, ok := loadOwnedEtape(w, r, deps)
		if !ok {
			return
		}
		ctx := r.Context()
		if err := r.ParseForm(); err != nil {
			http.Error(w, "bad request", http.StatusBadRequest)
			return
		}
		title := strings.TrimSpace(r.PostFormValue("title"))
		description := strings.TrimSpace(r.PostFormValue("description"))
		form := templates.EtapeUpdateForm{Title: title, Description: description}
		var errs templates.EtapeUpdateErrors
		if title == "" {
			errs.Title = "Title is required"
		} else if len(title) > 255 {
			errs.Title = "Title must be 255 characters or fewer"
		}
		if errs.Title != "" {
			w.Header().Set("Content-Type", "text/html; charset=utf-8")
			if r.Header.Get("HX-Request") == "true" {
				w.Header().Set("HX-Retarget", "#etape-form-slot")
				w.Header().Set("HX-Reswap", "innerHTML")
			}
			w.WriteHeader(http.StatusUnprocessableEntity)
			_ = templates.EtapeEditFormFragment(tablo.ID, etape, form, errs, csrf.Token(r)).Render(ctx, w)
			return
		}
		if _, err := deps.Queries.UpdateEtape(ctx, sqlc.UpdateEtapeParams{
			ID:          etape.ID,
			TabloID:     tablo.ID,
			Title:       title,
			Description: pgtype.Text{String: description, Valid: description != ""},
		}); err != nil {
			slog.Default().Error("etapes update: UpdateEtape failed", "tablo_id", tablo.ID, "etape_id", etape.ID, "err", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}
		renderTasksTab(w, r, deps.Queries, tablo)
	}
}

func EtapeDeleteConfirmHandler(deps EtapesDeps) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tablo, etape, ok := loadOwnedEtape(w, r, deps)
		if !ok {
			return
		}
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		_ = templates.EtapeDeleteConfirmFragment(tablo.ID, etape, csrf.Token(r)).Render(r.Context(), w)
	}
}

func EtapeDeleteHandler(deps EtapesDeps) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tablo, etape, ok := loadOwnedEtape(w, r, deps)
		if !ok {
			return
		}
		if err := deps.Queries.DeleteEtape(r.Context(), sqlc.DeleteEtapeParams{ID: etape.ID, TabloID: tablo.ID}); err != nil {
			slog.Default().Error("etapes delete: DeleteEtape failed", "tablo_id", tablo.ID, "etape_id", etape.ID, "err", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}
		renderTasksTab(w, r, deps.Queries, tablo)
	}
}

func EtapeReorderHandler(deps EtapesDeps) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tablo, _, ok := loadOwnedTablo(w, r, TablosDeps{Queries: deps.Queries})
		if !ok {
			return
		}
		if err := r.ParseForm(); err != nil {
			http.Error(w, "bad request", http.StatusBadRequest)
			return
		}
		for i, rawID := range r.Form["etape_id"] {
			etapeID, err := uuid.Parse(rawID)
			if err != nil {
				continue
			}
			if _, err := deps.Queries.GetEtapeByID(r.Context(), sqlc.GetEtapeByIDParams{ID: etapeID, TabloID: tablo.ID}); err != nil {
				if errors.Is(err, pgx.ErrNoRows) {
					continue
				}
				slog.Default().Error("etapes reorder: GetEtapeByID failed", "tablo_id", tablo.ID, "etape_id", etapeID, "err", err)
				http.Error(w, "internal server error", http.StatusInternalServerError)
				return
			}
			if _, err := deps.Queries.UpdateEtapePosition(r.Context(), sqlc.UpdateEtapePositionParams{
				ID:       etapeID,
				TabloID:  tablo.ID,
				Position: int32((i + 1) * 100),
			}); err != nil {
				slog.Default().Error("etapes reorder: UpdateEtapePosition failed", "tablo_id", tablo.ID, "etape_id", etapeID, "err", err)
				http.Error(w, "internal server error", http.StatusInternalServerError)
				return
			}
		}
		renderTasksTab(w, r, deps.Queries, tablo)
	}
}

func EtapeCancelNewHandler(deps EtapesDeps) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if _, _, ok := loadOwnedTablo(w, r, TablosDeps{Queries: deps.Queries}); !ok {
			return
		}
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		_, _ = w.Write([]byte(""))
	}
}

func EtapeCreateHandler(deps EtapesDeps) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tablo, _, ok := loadOwnedTablo(w, r, TablosDeps{Queries: deps.Queries})
		if !ok {
			return
		}
		ctx := r.Context()
		if err := r.ParseForm(); err != nil {
			http.Error(w, "bad request", http.StatusBadRequest)
			return
		}

		title := strings.TrimSpace(r.PostFormValue("title"))
		description := strings.TrimSpace(r.PostFormValue("description"))
		form := templates.EtapeCreateForm{Title: title, Description: description}
		var errs templates.EtapeCreateErrors
		if title == "" {
			errs.Title = "Title is required"
		} else if len(title) > 255 {
			errs.Title = "Title must be 255 characters or fewer"
		}
		if errs.Title != "" {
			w.Header().Set("Content-Type", "text/html; charset=utf-8")
			if r.Header.Get("HX-Request") == "true" {
				w.Header().Set("HX-Retarget", "#etape-form-slot")
				w.Header().Set("HX-Reswap", "innerHTML")
			}
			w.WriteHeader(http.StatusUnprocessableEntity)
			_ = templates.EtapeCreateFormFragment(tablo.ID, form, errs, csrf.Token(r)).Render(ctx, w)
			return
		}

		maxPos, err := deps.Queries.MaxEtapePositionByTablo(ctx, tablo.ID)
		if err != nil {
			slog.Default().Error("etapes create: MaxEtapePositionByTablo failed", "tablo_id", tablo.ID, "err", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}
		if _, err := deps.Queries.InsertEtape(ctx, sqlc.InsertEtapeParams{
			TabloID:     tablo.ID,
			Title:       title,
			Description: pgtype.Text{String: description, Valid: description != ""},
			Position:    maxPos + 100,
		}); err != nil {
			slog.Default().Error("etapes create: InsertEtape failed", "tablo_id", tablo.ID, "err", err)
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}

		renderTasksTab(w, r, deps.Queries, tablo)
	}
}
