package handlers

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/google/uuid"
	tablomodel "xtablo-backend/internal/tablos"
	"xtablo-backend/internal/web/dates"
	"xtablo-backend/internal/web/tabloicons"
	"xtablo-backend/internal/web/views"
)

var ErrTabloNotFound = tablomodel.ErrNotFound

var tabloColorPattern = regexp.MustCompile(`^#[0-9A-Fa-f]{6}$`)

const defaultTabloColor = "#3B82F6"
const tabloColorValidationMessage = "La couleur du projet doit être un code hexadécimal au format #RRGGBB"

type TabloStatus = tablomodel.Status

const (
	TabloStatusTodo       = tablomodel.StatusTodo
	TabloStatusInProgress = tablomodel.StatusInProgress
	TabloStatusDone       = tablomodel.StatusDone
)

type TabloRecord = tablomodel.Record
type CreateTabloInput = tablomodel.CreateInput
type UpdateTabloInput = tablomodel.UpdateInput
type ListTablosInput = tablomodel.ListInput

type TablosPageState struct {
	View           string
	Status         string
	ModalKind      string
	EditingTabloID string
}

func parseTablosPageState(values interface {
	Get(string) string
}) TablosPageState {
	view := strings.TrimSpace(values.Get("view"))
	if view != "list" {
		view = "grid"
	}

	status := strings.TrimSpace(values.Get("status"))
	switch status {
	case "todo", "in_progress", "done":
	default:
		status = "all"
	}

	return TablosPageState{
		View:      view,
		Status:    status,
		ModalKind: normalizedModalKind(strings.TrimSpace(values.Get("modal"))),
	}
}

func (s TablosPageState) statusFilter() *TabloStatus {
	switch s.Status {
	case string(TabloStatusTodo):
		status := TabloStatusTodo
		return &status
	case string(TabloStatusInProgress):
		status := TabloStatusInProgress
		return &status
	case string(TabloStatusDone):
		status := TabloStatusDone
		return &status
	default:
		return nil
	}
}

func normalizedModalKind(kind string) string {
	switch kind {
	case "create", "edit":
		return kind
	default:
		return ""
	}
}

func normalizeTabloColor(raw string) (string, bool) {
	color := strings.TrimSpace(raw)
	if !tabloColorPattern.MatchString(color) {
		return "", false
	}
	return strings.ToUpper(color), true
}

func storedTabloColor(raw string) string {
	if color, ok := normalizeTabloColor(raw); ok {
		return color
	}
	return defaultTabloColor
}

func (h *AuthHandler) PostTablos() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		user, ok := h.authenticatedUser(r.Context(), r)
		if !ok {
			http.Redirect(w, r, "/login", http.StatusSeeOther)
			return
		}

		if err := r.ParseForm(); err != nil {
			http.Error(w, "invalid form payload", http.StatusBadRequest)
			return
		}

		state := parseTablosPageState(r.Form)
		state.ModalKind = "create"

		name := strings.TrimSpace(r.FormValue("name"))
		if name == "" {
			tablos, err := listTablosForState(r.Context(), h.repo, user.ID, state)
			if err != nil {
				http.Error(w, "failed to list tablos", http.StatusInternalServerError)
				return
			}
			renderTablosResponse(w, r, "/tablos", tablos, tablosPageViewModel(user, state, tablos, name, r.FormValue("color"), "Le nom du projet est requis"), http.StatusUnprocessableEntity)
			return
		}

		color, ok := normalizeTabloColor(r.FormValue("color"))
		if !ok {
			tablos, err := listTablosForState(r.Context(), h.repo, user.ID, state)
			if err != nil {
				http.Error(w, "failed to list tablos", http.StatusInternalServerError)
				return
			}
			renderTablosResponse(w, r, "/tablos", tablos, tablosPageViewModel(user, state, tablos, name, r.FormValue("color"), tabloColorValidationMessage), http.StatusUnprocessableEntity)
			return
		}

		if _, err := h.repo.CreateTablo(r.Context(), CreateTabloInput{
			OwnerID: user.ID,
			Name:    name,
			Color:   color,
			Status:  TabloStatusTodo,
		}); err != nil {
			http.Error(w, "failed to create tablo", http.StatusInternalServerError)
			return
		}

		state.ModalKind = ""
		tablos, err := listTablosForState(r.Context(), h.repo, user.ID, state)
		if err != nil {
			http.Error(w, "failed to list tablos", http.StatusInternalServerError)
			return
		}

		renderTablosResponse(w, r, "/tablos", tablos, tablosPageViewModel(user, state, tablos, "", "", ""), http.StatusOK)
	}
}

func (h *AuthHandler) GetEditTabloModal() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		user, ok := h.authenticatedUser(r.Context(), r)
		if !ok {
			http.Redirect(w, r, "/login", http.StatusSeeOther)
			return
		}

		tabloID, err := uuid.Parse(r.PathValue("tabloID"))
		if err != nil {
			http.Error(w, "invalid tablo id", http.StatusBadRequest)
			return
		}

		state := parseTablosPageState(r.URL.Query())
		state.ModalKind = "edit"
		state.EditingTabloID = tabloID.String()

		tablos, err := listTablosForState(r.Context(), h.repo, user.ID, state)
		if err != nil {
			http.Error(w, "failed to list tablos", http.StatusInternalServerError)
			return
		}

		tablo, ok := findTabloByID(tablos, tabloID)
		if !ok {
			http.Error(w, "tablo not found", http.StatusNotFound)
			return
		}

		renderTablosResponse(w, r, "/tablos", tablos, tablosPageViewModel(user, state, tablos, tablo.Name, tablo.Color, ""), http.StatusOK)
	}
}

func (h *AuthHandler) PostTabloUpdate() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		user, ok := h.authenticatedUser(r.Context(), r)
		if !ok {
			http.Redirect(w, r, "/login", http.StatusSeeOther)
			return
		}

		tabloID, err := uuid.Parse(r.PathValue("tabloID"))
		if err != nil {
			http.Error(w, "invalid tablo id", http.StatusBadRequest)
			return
		}

		if err := r.ParseForm(); err != nil {
			http.Error(w, "invalid form payload", http.StatusBadRequest)
			return
		}

		state := parseTablosPageState(r.Form)
		state.ModalKind = "edit"
		state.EditingTabloID = tabloID.String()

		name := strings.TrimSpace(r.FormValue("name"))
		if name == "" {
			tablos, err := listTablosForState(r.Context(), h.repo, user.ID, state)
			if err != nil {
				http.Error(w, "failed to list tablos", http.StatusInternalServerError)
				return
			}
			renderTablosResponse(w, r, "/tablos", tablos, tablosPageViewModel(user, state, tablos, name, r.FormValue("color"), "Le nom du projet est requis"), http.StatusUnprocessableEntity)
			return
		}

		color, colorOK := normalizeTabloColor(r.FormValue("color"))
		if !colorOK {
			tablos, err := listTablosForState(r.Context(), h.repo, user.ID, state)
			if err != nil {
				http.Error(w, "failed to list tablos", http.StatusInternalServerError)
				return
			}
			renderTablosResponse(w, r, "/tablos", tablos, tablosPageViewModel(user, state, tablos, name, r.FormValue("color"), tabloColorValidationMessage), http.StatusUnprocessableEntity)
			return
		}

		if err := h.repo.UpdateTablo(r.Context(), UpdateTabloInput{
			ID:      tabloID,
			OwnerID: user.ID,
			Name:    name,
			Color:   color,
		}); err != nil {
			if errors.Is(err, ErrTabloNotFound) {
				http.Error(w, "tablo not found", http.StatusNotFound)
				return
			}
			http.Error(w, "failed to update tablo", http.StatusInternalServerError)
			return
		}

		state.ModalKind = ""
		state.EditingTabloID = ""
		tablos, err := listTablosForState(r.Context(), h.repo, user.ID, state)
		if err != nil {
			http.Error(w, "failed to list tablos", http.StatusInternalServerError)
			return
		}

		renderTablosResponse(w, r, "/tablos", tablos, tablosPageViewModel(user, state, tablos, "", "", ""), http.StatusOK)
	}
}

func (h *AuthHandler) DeleteTablo() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		user, ok := h.authenticatedUser(r.Context(), r)
		if !ok {
			http.Redirect(w, r, "/login", http.StatusSeeOther)
			return
		}

		tabloID, err := uuid.Parse(r.PathValue("tabloID"))
		if err != nil {
			http.Error(w, "invalid tablo id", http.StatusBadRequest)
			return
		}

		if err := h.repo.SoftDeleteTablo(r.Context(), tabloID, user.ID); err != nil {
			if errors.Is(err, ErrTabloNotFound) {
				http.Error(w, "tablo not found", http.StatusNotFound)
				return
			}
			http.Error(w, "failed to delete tablo", http.StatusInternalServerError)
			return
		}

		state := parseTablosPageState(r.URL.Query())
		tablos, err := listTablosForState(r.Context(), h.repo, user.ID, state)
		if err != nil {
			http.Error(w, "failed to list tablos", http.StatusInternalServerError)
			return
		}

		renderTablosResponse(w, r, "/tablos", tablos, tablosPageViewModel(user, state, tablos, "", "", ""), http.StatusOK)
	}
}

func (h *AuthHandler) renderTablosPage(w http.ResponseWriter, r *http.Request) {
	user, ok := h.authenticatedUser(r.Context(), r)
	if !ok {
		http.Redirect(w, r, "/login", http.StatusSeeOther)
		return
	}

	state := parseTablosPageState(r.URL.Query())
	tablos, err := listTablosForState(r.Context(), h.repo, user.ID, state)
	if err != nil {
		http.Error(w, "failed to list tablos", http.StatusInternalServerError)
		return
	}

	renderTablosResponse(w, r, "/tablos", tablos, tablosPageViewModel(user, state, tablos, "", "", ""), http.StatusOK)
}

func tablosPageViewModel(user PublicUser, state TablosPageState, tablos []TabloRecord, formName string, formColor string, errorMessage string) views.TablosPageViewModel {
	return views.NewTablosPageViewModel(
		user.DisplayName,
		state.View,
		state.Status,
		state.ModalKind,
		state.EditingTabloID,
		formName,
		formColor,
		errorMessage,
		buildTabloCardViews(tablos, state),
	)
}

func listTablosForState(ctx context.Context, repo AuthRepository, ownerID uuid.UUID, state TablosPageState) ([]TabloRecord, error) {
	return repo.ListTablos(ctx, ListTablosInput{
		OwnerID: ownerID,
		Status:  state.statusFilter(),
	})
}

func findTabloByID(tablos []TabloRecord, targetID uuid.UUID) (TabloRecord, bool) {
	for _, tablo := range tablos {
		if tablo.ID == targetID {
			return tablo, true
		}
	}
	return TabloRecord{}, false
}

func renderTablosResponse(w http.ResponseWriter, r *http.Request, activePath string, tablos []TabloRecord, vm views.TablosPageViewModel, statusCode int) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(statusCode)

	var err error
	content := views.TablosPageContent(vm)
	if isHXRequest(r) {
		err = views.DashboardContentSwapWithMainClass(activePath, tablos, "flex-1 overflow-auto", content).Render(r.Context(), w)
	} else {
		err = views.DashboardPageWithMainClass(activePath, tablos, "flex-1 overflow-auto", content).Render(r.Context(), w)
	}
	if err != nil {
		http.Error(w, "failed to render tablos page", http.StatusInternalServerError)
	}
}

func (r *InMemoryAuthRepository) CreateTablo(_ context.Context, input CreateTabloInput) (TabloRecord, error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	now := time.Now().UTC()
	tablo := TabloRecord{
		ID:        uuid.New(),
		OwnerID:   input.OwnerID,
		Name:      strings.TrimSpace(input.Name),
		Color:     storedTabloColor(input.Color),
		Status:    input.Status,
		CreatedAt: now,
		UpdatedAt: now,
	}

	r.tablos[tablo.ID] = tablo
	return tablo, nil
}

func (r *InMemoryAuthRepository) ListTablos(_ context.Context, input ListTablosInput) ([]TabloRecord, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	var tablos []TabloRecord

	for _, tablo := range r.tablos {
		if tablo.OwnerID != input.OwnerID {
			continue
		}
		if tablo.DeletedAt != nil {
			continue
		}
		if input.Status != nil && tablo.Status != *input.Status {
			continue
		}

		tablos = append(tablos, tablo)
	}

	sortTablosByCreatedAtDesc(tablos)
	return tablos, nil
}

func (r *InMemoryAuthRepository) UpdateTablo(_ context.Context, input UpdateTabloInput) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	tablo, ok := r.tablos[input.ID]
	if !ok || tablo.OwnerID != input.OwnerID || tablo.DeletedAt != nil {
		return ErrTabloNotFound
	}

	tablo.Name = strings.TrimSpace(input.Name)
	tablo.Color = storedTabloColor(input.Color)
	tablo.UpdatedAt = time.Now().UTC()
	r.tablos[input.ID] = tablo
	return nil
}

func (r *InMemoryAuthRepository) SoftDeleteTablo(_ context.Context, tabloID uuid.UUID, ownerID uuid.UUID) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	tablo, ok := r.tablos[tabloID]
	if !ok || tablo.OwnerID != ownerID || tablo.DeletedAt != nil {
		return ErrTabloNotFound
	}

	now := time.Now().UTC()
	tablo.DeletedAt = &now
	tablo.UpdatedAt = now
	r.tablos[tabloID] = tablo
	return nil
}

func sortTablosByCreatedAtDesc(tablos []TabloRecord) {
	for i := 0; i < len(tablos); i++ {
		for j := i + 1; j < len(tablos); j++ {
			if tablos[j].CreatedAt.After(tablos[i].CreatedAt) {
				tablos[i], tablos[j] = tablos[j], tablos[i]
			}
		}
	}
}

func buildTabloCardViews(tablos []TabloRecord, state TablosPageState) []views.TabloCardView {
	items := make([]views.TabloCardView, 0, len(tablos))
	for _, tablo := range tablos {
		statusLabel, statusClass, progress, statusTone := tabloStatusPresentation(tablo.Status)
		color := storedTabloColor(tablo.Color)
		iconKind, bgClass, fgClass, accent := tabloIconPresentation(color)

		items = append(items, views.TabloCardView{
			ID:               tablo.ID.String(),
			Name:             tablo.Name,
			Color:            color,
			Status:           string(tablo.Status),
			StatusLabel:      statusLabel,
			StatusClass:      statusClass,
			StatusTone:       statusTone,
			Progress:         progress,
			CreatedAtLabel:   formatFrenchDate(tablo.CreatedAt),
			CardDateLabel:    formatCardDate(tablo.CreatedAt),
			ProgressLabel:    fmt.Sprintf("%d%%", progress),
			DeleteURL:        "/tablos/" + tablo.ID.String(),
			DeleteRequestURL: buildDeleteRequestURL("/tablos/"+tablo.ID.String(), state),
			EditRequestURL:   buildEditRequestURL("/tablos/"+tablo.ID.String()+"/edit", state),
			IconKind:         iconKind,
			IconBgClass:      bgClass,
			IconFgClass:      fgClass,
			Accent:           accent,
			Initial:          projectInitial(tablo.Name),
		})
	}
	return items
}

func buildDeleteRequestURL(path string, state TablosPageState) string {
	return buildStatefulRequestURL(path, state)
}

func buildEditRequestURL(path string, state TablosPageState) string {
	return buildStatefulRequestURL(path, state)
}

func buildStatefulRequestURL(path string, state TablosPageState) string {
	values := url.Values{}
	values.Set("view", state.View)
	values.Set("status", state.Status)
	encoded := values.Encode()
	if encoded == "" {
		return path
	}
	return path + "?" + encoded
}

func tabloStatusPresentation(status TabloStatus) (string, string, int, string) {
	switch status {
	case TabloStatusInProgress:
		return "En cours", "bg-[#FFF4E2] text-[#DB9729] border border-[#DB9729]", 50, "warning"
	case TabloStatusDone:
		return "Terminé", "bg-green-50 text-green-600 border border-green-200 dark:bg-green-950/30 dark:text-green-400 dark:border-green-800", 100, "success"
	default:
		return "À faire", "bg-blue-50 text-blue-600 border border-blue-200 dark:bg-blue-950/30 dark:text-blue-400 dark:border-blue-800", 0, "info"
	}
}

func tabloIconPresentation(color string) (string, string, string, string) {
	presentation := tabloicons.ForColor(color)
	return presentation.Icon, presentation.Bg, presentation.Fg, presentation.Accent
}

func formatFrenchDate(value time.Time) string {
	return dates.FormatFrenchDate(value)
}

func formatCardDate(value time.Time) string {
	return dates.FormatFrenchDate(value)
}

func projectInitial(name string) string {
	trimmed := strings.TrimSpace(name)
	if trimmed == "" {
		return "P"
	}
	runes := []rune(trimmed)
	return strings.ToUpper(string(runes[0]))
}
