package handlers

// Submission section handlers — Composer Slice B (t-paliad-313). Backs
// the inline editor on /projects/{id}/submissions/{code}/draft/{draft_id}
// where the lawyer types prose into each section.
//
// Endpoint:
//
//   PATCH /api/submission-drafts/{draft_id}/sections/{section_id}
//
// Body shape (all fields optional — absent = no change):
//
//   {
//     "content_md_de": "...",
//     "content_md_en": "...",
//     "included": true|false,
//     "label_de": "...",
//     "label_en": "...",
//     "order_index": 3
//   }
//
// Visibility: ownership of the draft is checked via
// SubmissionDraftService.Get (404 on no-access), then the section is
// fetched + verified to belong to that draft. The DB-side RLS policy
// (mig 148) enforces the same gate independently.
//
// Returns 200 + the refreshed section row on success.
//
// This is global-scoped (no /projects/{id}/ prefix) because the
// section's owning draft already carries the project_id; routing on
// section_id alone keeps the URL shape stable across project-scoped
// and project-less drafts.

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"time"

	"github.com/google/uuid"

	"mgit.msbls.de/m/paliad/internal/services"
)

// submissionSectionPatchInput is the JSON shape accepted by PATCH.
type submissionSectionPatchInput struct {
	ContentMDDE *string `json:"content_md_de,omitempty"`
	ContentMDEN *string `json:"content_md_en,omitempty"`
	Included    *bool   `json:"included,omitempty"`
	LabelDE     *string `json:"label_de,omitempty"`
	LabelEN     *string `json:"label_en,omitempty"`
	OrderIndex  *int    `json:"order_index,omitempty"`
}

// submissionSectionPatchTimeout caps the round-trip.
const submissionSectionPatchTimeout = 10 * time.Second

func handlePatchSubmissionSection(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	if dbSvc.submissionDraft == nil || dbSvc.submissionSection == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "submission sections not configured"})
		return
	}
	draftID, ok := parseUUIDPath(w, r, "draft_id", "draft id")
	if !ok {
		return
	}
	sectionID, ok := parseUUIDPath(w, r, "section_id", "section id")
	if !ok {
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), submissionSectionPatchTimeout)
	defer cancel()

	// Owner-scope on the draft (RLS mirror; this gives us the typed
	// 404 + the path for the "section belongs to a different draft"
	// case below).
	draft, err := dbSvc.submissionDraft.Get(ctx, uid, draftID)
	if err != nil {
		writeSubmissionDraftServiceError(w, err)
		return
	}

	existing, err := dbSvc.submissionSection.Get(ctx, sectionID)
	if err != nil {
		if errors.Is(err, services.ErrSubmissionSectionNotFound) {
			writeJSON(w, http.StatusNotFound, map[string]string{"error": "section not found"})
			return
		}
		writeServiceError(w, err)
		return
	}
	if existing.DraftID != draft.ID {
		// Section exists but doesn't belong to this draft — surface as
		// 404 to keep the "no fishing for foreign drafts" property.
		writeJSON(w, http.StatusNotFound, map[string]string{"error": "section not found"})
		return
	}

	var input submissionSectionPatchInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
		return
	}

	patch := services.SectionPatch{
		ContentMDDE: input.ContentMDDE,
		ContentMDEN: input.ContentMDEN,
		Included:    input.Included,
		LabelDE:     input.LabelDE,
		LabelEN:     input.LabelEN,
		OrderIndex:  input.OrderIndex,
	}
	updated, err := dbSvc.submissionSection.Update(ctx, sectionID, patch)
	if err != nil {
		if errors.Is(err, services.ErrSubmissionSectionNotFound) {
			writeJSON(w, http.StatusNotFound, map[string]string{"error": "section not found"})
			return
		}
		writeServiceError(w, err)
		return
	}

	writeJSON(w, http.StatusOK, sectionJSONFromService(updated))
}

// ─────────────────────────────────────────────────────────────────────
// Slice F — add custom section / delete section / reorder
// ─────────────────────────────────────────────────────────────────────

type submissionSectionCreateInput struct {
	SectionKey  string `json:"section_key"`
	Kind        string `json:"kind"`
	LabelDE     string `json:"label_de"`
	LabelEN     string `json:"label_en"`
	ContentMDDE string `json:"content_md_de,omitempty"`
	ContentMDEN string `json:"content_md_en,omitempty"`
	OrderIndex  int    `json:"order_index,omitempty"`
}

// handleCreateSubmissionSection backs POST /api/submission-drafts/{draft_id}/sections.
// Adds a new (custom) section to the draft. Owner-scoped via
// SubmissionDraftService.Get.
func handleCreateSubmissionSection(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	if dbSvc.submissionDraft == nil || dbSvc.submissionSection == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "submission sections not configured"})
		return
	}
	draftID, ok := parseUUIDPath(w, r, "draft_id", "draft id")
	if !ok {
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), submissionSectionPatchTimeout)
	defer cancel()

	if _, err := dbSvc.submissionDraft.Get(ctx, uid, draftID); err != nil {
		writeSubmissionDraftServiceError(w, err)
		return
	}

	var input submissionSectionCreateInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
		return
	}
	created, err := dbSvc.submissionSection.Create(ctx, services.SectionCreateInput{
		DraftID:     draftID,
		SectionKey:  input.SectionKey,
		Kind:        input.Kind,
		LabelDE:     input.LabelDE,
		LabelEN:     input.LabelEN,
		ContentMDDE: input.ContentMDDE,
		ContentMDEN: input.ContentMDEN,
		OrderIndex:  input.OrderIndex,
		Included:    true,
	})
	if err != nil {
		if errors.Is(err, services.ErrInvalidInput) {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
			return
		}
		writeServiceError(w, err)
		return
	}
	writeJSON(w, http.StatusCreated, sectionJSONFromService(created))
}

// handleDeleteSubmissionSection backs DELETE /api/submission-drafts/{draft_id}/sections/{section_id}.
// Owner-scoped via SubmissionDraftService.Get + section-belongs-to-draft cross-check.
func handleDeleteSubmissionSection(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	if dbSvc.submissionDraft == nil || dbSvc.submissionSection == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "submission sections not configured"})
		return
	}
	draftID, ok := parseUUIDPath(w, r, "draft_id", "draft id")
	if !ok {
		return
	}
	sectionID, ok := parseUUIDPath(w, r, "section_id", "section id")
	if !ok {
		return
	}
	ctx, cancel := context.WithTimeout(r.Context(), submissionSectionPatchTimeout)
	defer cancel()

	draft, err := dbSvc.submissionDraft.Get(ctx, uid, draftID)
	if err != nil {
		writeSubmissionDraftServiceError(w, err)
		return
	}
	sec, err := dbSvc.submissionSection.Get(ctx, sectionID)
	if err != nil {
		if errors.Is(err, services.ErrSubmissionSectionNotFound) {
			writeJSON(w, http.StatusNotFound, map[string]string{"error": "section not found"})
			return
		}
		writeServiceError(w, err)
		return
	}
	if sec.DraftID != draft.ID {
		writeJSON(w, http.StatusNotFound, map[string]string{"error": "section not found"})
		return
	}
	if err := dbSvc.submissionSection.Delete(ctx, sectionID); err != nil {
		if errors.Is(err, services.ErrSubmissionSectionNotFound) {
			writeJSON(w, http.StatusNotFound, map[string]string{"error": "section not found"})
			return
		}
		writeServiceError(w, err)
		return
	}
	writeJSON(w, http.StatusNoContent, nil)
}

type submissionSectionReorderInput struct {
	SectionOrder []string `json:"section_order"`
}

// handleReorderSubmissionSections backs POST /api/submission-drafts/{draft_id}/sections/reorder.
// Accepts a sequence of section_ids; rewrites every row's order_index
// to (1, 2, 3, …) × 10 in the supplied order. Returns the refreshed
// section list.
func handleReorderSubmissionSections(w http.ResponseWriter, r *http.Request) {
	if !requireDB(w) {
		return
	}
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}
	if dbSvc.submissionDraft == nil || dbSvc.submissionSection == nil {
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "submission sections not configured"})
		return
	}
	draftID, ok := parseUUIDPath(w, r, "draft_id", "draft id")
	if !ok {
		return
	}
	ctx, cancel := context.WithTimeout(r.Context(), submissionSectionPatchTimeout)
	defer cancel()

	if _, err := dbSvc.submissionDraft.Get(ctx, uid, draftID); err != nil {
		writeSubmissionDraftServiceError(w, err)
		return
	}

	var input submissionSectionReorderInput
	if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
		return
	}
	order := make([]uuid.UUID, 0, len(input.SectionOrder))
	for _, raw := range input.SectionOrder {
		id, err := uuid.Parse(raw)
		if err != nil {
			writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid section id in order list"})
			return
		}
		order = append(order, id)
	}

	rows, err := dbSvc.submissionSection.Reorder(ctx, draftID, order)
	if err != nil {
		writeServiceError(w, err)
		return
	}
	out := make([]submissionSectionJSON, 0, len(rows))
	for _, sec := range rows {
		out = append(out, sectionJSONFromService(&sec))
	}
	writeJSON(w, http.StatusOK, map[string]any{"sections": out})
}

// sectionJSONFromService projects a services.SubmissionSection into the
// JSON shape the editor consumes — the same shape buildSubmissionDraftView
// emits under .sections[].
func sectionJSONFromService(sec *services.SubmissionSection) submissionSectionJSON {
	return submissionSectionJSON{
		ID:          sec.ID,
		SectionKey:  sec.SectionKey,
		OrderIndex:  sec.OrderIndex,
		Kind:        sec.Kind,
		LabelDE:     sec.LabelDE,
		LabelEN:     sec.LabelEN,
		Included:    sec.Included,
		ContentMDDE: sec.ContentMDDE,
		ContentMDEN: sec.ContentMDEN,
	}
}
