package handlers

import (
	"context"
	"errors"
	"fmt"
	"html"
	"net/http"
	"strconv"
	"strings"
	"time"

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

type TaskStatus = taskmodel.Status

const (
	TaskStatusTodo       = taskmodel.StatusTodo
	TaskStatusInProgress = taskmodel.StatusInProgress
	TaskStatusInReview   = taskmodel.StatusInReview
	TaskStatusDone       = taskmodel.StatusDone
)

type TaskRecord = taskmodel.Record
type CreateTaskInput = taskmodel.CreateInput
type UpdateTaskInput = taskmodel.UpdateInput
type ListTasksByTabloInput = taskmodel.ListByTabloInput

type taskPageRepository interface {
	ListTasksByOwner(ctx context.Context, ownerID uuid.UUID) ([]TaskRecord, error)
}

type taskMutationRepository interface {
	CreateTask(ctx context.Context, input CreateTaskInput) (TaskRecord, error)
	GetTaskByID(ctx context.Context, taskID uuid.UUID, ownerID uuid.UUID) (TaskRecord, error)
	UpdateTask(ctx context.Context, input UpdateTaskInput) (TaskRecord, error)
	SoftDeleteTask(ctx context.Context, taskID uuid.UUID, ownerID uuid.UUID) error
}

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

	tablos, err := h.repo.ListTablos(r.Context(), ListTablosInput{
		OwnerID: user.ID,
	})
	if err != nil {
		http.Error(w, "failed to load projects", http.StatusInternalServerError)
		return
	}

	taskRepo, ok := h.repo.(taskPageRepository)
	if !ok {
		http.Error(w, "tasks repository not configured", http.StatusInternalServerError)
		return
	}

	tasks, err := taskRepo.ListTasksByOwner(r.Context(), user.ID)
	if err != nil {
		http.Error(w, "failed to load tasks", http.StatusInternalServerError)
		return
	}

	assigneeLabels := h.buildAssigneeLabels(r.Context(), user.ID, tasks)
	state := parseTaskPageState(r)
	vm := views.NewTasksPageViewModel(tablos, tasks, assigneeLabels, state, time.Now().UTC())

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	content := views.TasksPageContent(vm)
	var renderErr error
	if isHXRequest(r) {
		renderErr = views.DashboardContentSwap("/tasks", tablos, content).Render(r.Context(), w)
	} else {
		renderErr = views.DashboardPage("/tasks", tablos, content).Render(r.Context(), w)
	}
	if renderErr != nil {
		http.Error(w, "failed to render tasks page", http.StatusInternalServerError)
	}
}

func (h *AuthHandler) PostTasks() 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
		}

		repo, ok := h.repo.(taskMutationRepository)
		if !ok {
			http.Error(w, "tasks repository not configured", http.StatusInternalServerError)
			return
		}

		input, err := parseCreateTaskInput(r, user.ID)
		if err != nil {
			http.Error(w, err.Error(), http.StatusUnprocessableEntity)
			return
		}

		taskRecords, err := h.repo.(taskPageRepository).ListTasksByOwner(r.Context(), user.ID)
		if err != nil {
			http.Error(w, "failed to load tasks", http.StatusInternalServerError)
			return
		}
		if err := validateTaskTabloAndParent(taskRecords, input.TabloID, input.ParentTaskID); err != nil {
			http.Error(w, err.Error(), http.StatusUnprocessableEntity)
			return
		}

		if _, err := repo.CreateTask(r.Context(), input); err != nil {
			if isTaskValidationError(err) {
				http.Error(w, err.Error(), http.StatusUnprocessableEntity)
				return
			}
			http.Error(w, "failed to create task", http.StatusInternalServerError)
			return
		}

		h.renderTasksPage(w, r)
	}
}

func (h *AuthHandler) GetEditTaskModal() 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
		}

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

		repo, ok := h.repo.(taskMutationRepository)
		if !ok {
			http.Error(w, "tasks repository not configured", http.StatusInternalServerError)
			return
		}

		record, err := repo.GetTaskByID(r.Context(), taskID, user.ID)
		if err != nil {
			if errors.Is(err, taskmodel.ErrNotFound) {
				http.Error(w, "task not found", http.StatusNotFound)
				return
			}
			http.Error(w, "failed to load task", http.StatusInternalServerError)
			return
		}

		tablos, err := h.repo.ListTablos(r.Context(), ListTablosInput{OwnerID: user.ID})
		if err != nil {
			http.Error(w, "failed to load projects", http.StatusInternalServerError)
			return
		}
		taskRecords, err := h.repo.(taskPageRepository).ListTasksByOwner(r.Context(), user.ID)
		if err != nil {
			http.Error(w, "failed to load tasks", http.StatusInternalServerError)
			return
		}
		assigneeLabels := h.buildAssigneeLabels(r.Context(), user.ID, taskRecords)

		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		_, _ = fmt.Fprint(w, renderTaskEditForm(record, tablos, taskRecords, assigneeLabels))
	}
}

func (h *AuthHandler) PatchTask() 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
		}

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

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

		repo, ok := h.repo.(taskMutationRepository)
		if !ok {
			http.Error(w, "tasks repository not configured", http.StatusInternalServerError)
			return
		}

		record, err := repo.GetTaskByID(r.Context(), taskID, user.ID)
		if err != nil {
			if errors.Is(err, taskmodel.ErrNotFound) {
				http.Error(w, "task not found", http.StatusNotFound)
				return
			}
			http.Error(w, "failed to load task", http.StatusInternalServerError)
			return
		}

		input, err := parseUpdateTaskInput(r, user.ID, record)
		if err != nil {
			http.Error(w, err.Error(), http.StatusUnprocessableEntity)
			return
		}

		taskRecords, err := h.repo.(taskPageRepository).ListTasksByOwner(r.Context(), user.ID)
		if err != nil {
			http.Error(w, "failed to load tasks", http.StatusInternalServerError)
			return
		}
		if err := validateTaskTabloAndParent(taskRecords, input.TabloID, input.ParentTaskID); err != nil {
			http.Error(w, err.Error(), http.StatusUnprocessableEntity)
			return
		}

		if _, err := repo.UpdateTask(r.Context(), input); err != nil {
			if isTaskValidationError(err) {
				http.Error(w, err.Error(), http.StatusUnprocessableEntity)
				return
			}
			http.Error(w, "failed to update task", http.StatusInternalServerError)
			return
		}

		h.renderTasksPage(w, r)
	}
}

func (h *AuthHandler) DeleteTask() 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
		}

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

		repo, ok := h.repo.(taskMutationRepository)
		if !ok {
			http.Error(w, "tasks repository not configured", http.StatusInternalServerError)
			return
		}

		if err := repo.SoftDeleteTask(r.Context(), taskID, user.ID); err != nil {
			if errors.Is(err, taskmodel.ErrNotFound) {
				http.Error(w, "task not found", http.StatusNotFound)
				return
			}
			http.Error(w, "failed to delete task", http.StatusInternalServerError)
			return
		}

		h.renderTasksPage(w, r)
	}
}

func parseCreateTaskInput(r *http.Request, ownerID uuid.UUID) (CreateTaskInput, error) {
	tabloID, err := uuid.Parse(strings.TrimSpace(r.FormValue("tablo_id")))
	if err != nil {
		return CreateTaskInput{}, errors.New("tablo_id invalide")
	}

	title := strings.TrimSpace(r.FormValue("title"))
	if title == "" {
		return CreateTaskInput{}, errors.New("le titre est requis")
	}

	status, err := parseTaskStatusFormValue(r.FormValue("status"))
	if err != nil {
		return CreateTaskInput{}, err
	}

	parentTaskID, err := parseOptionalUUID(r.FormValue("parent_task_id"))
	if err != nil {
		return CreateTaskInput{}, errors.New("parent_task_id invalide")
	}

	assigneeID, err := parseOptionalUUID(r.FormValue("assignee_id"))
	if err != nil {
		return CreateTaskInput{}, errors.New("assignee_id invalide")
	}

	dueDate, err := parseOptionalDate(r.FormValue("due_date"))
	if err != nil {
		return CreateTaskInput{}, errors.New("due_date invalide")
	}

	isEtape, _ := strconv.ParseBool(strings.TrimSpace(r.FormValue("is_etape")))

	return CreateTaskInput{
		OwnerID:      ownerID,
		TabloID:      tabloID,
		Title:        title,
		Description:  strings.TrimSpace(r.FormValue("description")),
		Status:       status,
		AssigneeID:   assigneeID,
		IsEtape:      isEtape,
		ParentTaskID: parentTaskID,
		DueDate:      dueDate,
	}, nil
}

func parseUpdateTaskInput(r *http.Request, ownerID uuid.UUID, current TaskRecord) (UpdateTaskInput, error) {
	tabloRaw := strings.TrimSpace(r.FormValue("tablo_id"))
	tabloID := current.TabloID
	if tabloRaw != "" {
		parsedTabloID, err := uuid.Parse(tabloRaw)
		if err != nil {
			return UpdateTaskInput{}, errors.New("tablo_id invalide")
		}
		tabloID = parsedTabloID
	}

	title := strings.TrimSpace(r.FormValue("title"))
	if title == "" {
		return UpdateTaskInput{}, errors.New("le titre est requis")
	}

	status, err := parseTaskStatusFormValue(r.FormValue("status"))
	if err != nil {
		return UpdateTaskInput{}, err
	}

	parentTaskID, err := parseOptionalUUID(r.FormValue("parent_task_id"))
	if err != nil {
		return UpdateTaskInput{}, errors.New("parent_task_id invalide")
	}

	assigneeID, err := parseOptionalUUID(r.FormValue("assignee_id"))
	if err != nil {
		return UpdateTaskInput{}, errors.New("assignee_id invalide")
	}

	dueDate, err := parseOptionalDate(r.FormValue("due_date"))
	if err != nil {
		return UpdateTaskInput{}, errors.New("due_date invalide")
	}

	if current.IsEtape {
		parentTaskID = nil
	}

	return UpdateTaskInput{
		ID:           current.ID,
		OwnerID:      ownerID,
		TabloID:      tabloID,
		Title:        title,
		Description:  strings.TrimSpace(r.FormValue("description")),
		Status:       status,
		AssigneeID:   assigneeID,
		ParentTaskID: parentTaskID,
		DueDate:      dueDate,
	}, nil
}

func parseTaskStatusFormValue(raw string) (TaskStatus, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return TaskStatusTodo, nil
	}
	status, err := taskmodel.ParseStatus(raw)
	if err != nil {
		return "", errors.New("status invalide")
	}
	return status, nil
}

func parseTaskPageState(r *http.Request) taskmodel.TaskPageState {
	query := r.URL.Query()
	state := taskmodel.TaskPageState{
		View:        taskmodel.NormalizeTaskView(query.Get("view")),
		RoadmapMode: taskmodel.NormalizeTaskRoadmapMode(query.Get("roadmap_mode")),
		TabloIDs:    parseUUIDList(query["tablo"]),
		AssigneeIDs: parseUUIDList(query["assignee"]),
		Statuses:    parseStatusList(query["status"]),
	}
	if state.View != taskmodel.TaskViewRoadmap {
		state.RoadmapMode = taskmodel.TaskRoadmapModeWeek
	}
	return state
}

func parseUUIDList(values []string) []uuid.UUID {
	parsed := make([]uuid.UUID, 0, len(values))
	for _, value := range values {
		id, err := uuid.Parse(strings.TrimSpace(value))
		if err == nil {
			parsed = append(parsed, id)
		}
	}
	return parsed
}

func parseStatusList(values []string) []taskmodel.Status {
	parsed := make([]taskmodel.Status, 0, len(values))
	for _, value := range values {
		status, err := taskmodel.ParseStatus(strings.TrimSpace(value))
		if err == nil {
			parsed = append(parsed, status)
		}
	}
	return parsed
}

func parseOptionalUUID(raw string) (*uuid.UUID, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil, nil
	}
	id, err := uuid.Parse(raw)
	if err != nil {
		return nil, err
	}
	return &id, nil
}

func parseOptionalDate(raw string) (*time.Time, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil, nil
	}
	value, err := time.Parse("2006-01-02", raw)
	if err != nil {
		return nil, err
	}
	return &value, nil
}

func optionalUUIDString(id *uuid.UUID) string {
	if id == nil {
		return ""
	}
	return id.String()
}

func isTaskValidationError(err error) bool {
	return errors.Is(err, taskmodel.ErrInvalidParent) || errors.Is(err, taskmodel.ErrInvalidAssignee) || errors.Is(err, taskmodel.ErrInvalidStatus)
}

func validateTaskTabloAndParent(records []TaskRecord, tabloID uuid.UUID, parentTaskID *uuid.UUID) error {
	if parentTaskID == nil {
		return nil
	}
	for _, record := range records {
		if record.ID != *parentTaskID {
			continue
		}
		if !record.IsEtape {
			return errors.New("parent_task_id must reference an étape")
		}
		if record.TabloID != tabloID {
			return errors.New("parent_task_id must belong to the selected tablo")
		}
		return nil
	}
	return errors.New("parent_task_id must reference an active étape")
}

func (h *AuthHandler) buildAssigneeLabels(ctx context.Context, ownerID uuid.UUID, tasks []TaskRecord) map[uuid.UUID]string {
	assigneeLabels := make(map[uuid.UUID]string)
	if publicUser, err := h.repo.GetPublicUserByID(ctx, ownerID); err == nil {
		assigneeLabels[ownerID] = publicUser.DisplayName
	}
	for _, record := range tasks {
		if record.AssigneeID == nil {
			continue
		}
		if _, exists := assigneeLabels[*record.AssigneeID]; exists {
			continue
		}
		publicUser, err := h.repo.GetPublicUserByID(ctx, *record.AssigneeID)
		if err != nil {
			continue
		}
		assigneeLabels[*record.AssigneeID] = publicUser.DisplayName
	}
	return assigneeLabels
}

func renderTaskEditForm(record TaskRecord, tablos []tablomodel.Record, tasks []TaskRecord, assigneeLabels map[uuid.UUID]string) string {
	var builder strings.Builder
	builder.WriteString(`<form class="task-edit-form" data-task-id="`)
	builder.WriteString(html.EscapeString(record.ID.String()))
	builder.WriteString(`">`)
	builder.WriteString(`<label>Tablo<select name="tablo_id">`)
	for _, tablo := range tablos {
		builder.WriteString(`<option value="`)
		builder.WriteString(html.EscapeString(tablo.ID.String()))
		builder.WriteString(`"`)
		if tablo.ID == record.TabloID {
			builder.WriteString(` selected`)
		}
		builder.WriteString(`>`)
		builder.WriteString(html.EscapeString(tablo.Name))
		builder.WriteString(`</option>`)
	}
	builder.WriteString(`</select></label>`)
	builder.WriteString(`<input name="title" value="`)
	builder.WriteString(html.EscapeString(record.Title))
	builder.WriteString(`">`)
	builder.WriteString(`<textarea name="description">`)
	builder.WriteString(html.EscapeString(record.Description))
	builder.WriteString(`</textarea>`)
	builder.WriteString(`<input name="status" value="`)
	builder.WriteString(html.EscapeString(string(record.Status)))
	builder.WriteString(`">`)
	builder.WriteString(`<input name="assignee_id" value="`)
	builder.WriteString(html.EscapeString(optionalUUIDString(record.AssigneeID)))
	builder.WriteString(`">`)
	builder.WriteString(`<select name="parent_task_id"><option value="">Sans étape</option>`)
	for _, task := range tasks {
		if !task.IsEtape || task.TabloID != record.TabloID {
			continue
		}
		builder.WriteString(`<option value="`)
		builder.WriteString(html.EscapeString(task.ID.String()))
		builder.WriteString(`"`)
		if record.ParentTaskID != nil && *record.ParentTaskID == task.ID {
			builder.WriteString(` selected`)
		}
		builder.WriteString(`>`)
		builder.WriteString(html.EscapeString(task.Title))
		builder.WriteString(`</option>`)
	}
	builder.WriteString(`</select>`)
	if len(assigneeLabels) > 0 {
		builder.WriteString(`<div class="task-assignees">`)
		for id, label := range assigneeLabels {
			builder.WriteString(`<span data-assignee-id="`)
			builder.WriteString(html.EscapeString(id.String()))
			builder.WriteString(`">`)
			builder.WriteString(html.EscapeString(label))
			builder.WriteString(`</span>`)
		}
		builder.WriteString(`</div>`)
	}
	builder.WriteString(`</form>`)
	return builder.String()
}
