package handlers

import (
	"net/http"
	"strconv"
	"strings"

	"github.com/google/uuid"

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

// t-paliad-346 / m/paliad#153 B3 — universal search for the Litigation
// Builder. Returns events + scenarios + projects (Akten) keyed by type
// so the search dropdown can render typed result groups.
//
// GET /api/builder/search?q=<term>&limit=<n>
//
// Response shape:
//
//	{
//	  "query":     "<echoed q>",
//	  "events":    [ EventSearchHit, ... ],   // anchor_rule_id + proceeding_type embedded
//	  "scenarios": [ { id, name, status, updated_at }, ... ],
//	  "projects":  [ { id, title, type, reference, case_number, matter_number, client_number }, ... ],
//	  "counts":    { "events": N, "scenarios": M, "projects": K }
//	}
//
// Each group is independently capped (default 8 events / 5 scenarios /
// 5 projects, max 30 per group). Missing services degrade gracefully —
// an unavailable group is returned as an empty array, not an error,
// so a knowledge-only deploy (DATABASE_URL unset) can still serve a
// best-effort empty response shape rather than a 503 wall.

type builderSearchScenarioHit struct {
	ID        uuid.UUID `json:"id"`
	Name      string    `json:"name"`
	Status    string    `json:"status"`
	UpdatedAt string    `json:"updated_at"`
}

type builderSearchProjectHit struct {
	ID            uuid.UUID `json:"id"`
	Type          string    `json:"type"`
	Title         string    `json:"title"`
	Reference     *string   `json:"reference,omitempty"`
	CaseNumber    *string   `json:"case_number,omitempty"`
	MatterNumber  *string   `json:"matter_number,omitempty"`
	ClientNumber  *string   `json:"client_number,omitempty"`
}

type builderSearchResponse struct {
	Query     string                       `json:"query"`
	Events    []services.EventSearchHit    `json:"events"`
	Scenarios []builderSearchScenarioHit   `json:"scenarios"`
	Projects  []builderSearchProjectHit    `json:"projects"`
	Counts    builderSearchCounts          `json:"counts"`
}

type builderSearchCounts struct {
	Events    int `json:"events"`
	Scenarios int `json:"scenarios"`
	Projects  int `json:"projects"`
}

// handleBuilderSearch — GET /api/builder/search?q=<term>&limit=<n>
//
// Auth required. Returns 200 with empty groups when q is empty (matches
// the fristenrechner search ergonomic — frontend can boot without a
// pre-flight round trip).
func handleBuilderSearch(w http.ResponseWriter, r *http.Request) {
	uid, ok := requireUser(w, r)
	if !ok {
		return
	}

	q := strings.TrimSpace(r.URL.Query().Get("q"))
	perGroupLimit := parseBuilderSearchLimit(r.URL.Query().Get("limit"))

	resp := builderSearchResponse{
		Query:     q,
		Events:    []services.EventSearchHit{},
		Scenarios: []builderSearchScenarioHit{},
		Projects:  []builderSearchProjectHit{},
	}

	if q == "" {
		// Match fristenrechner search: empty query → empty groups, not 400.
		writeJSON(w, http.StatusOK, resp)
		return
	}

	ctx := r.Context()

	// Events: reuse the SearchEvents shape so anchor_rule_id +
	// proceeding_type travel with each hit. UPC v1 (PRD §0.4) — the
	// jurisdiction filter pins the corpus the builder serves today.
	if dbSvc != nil && dbSvc.deadlineSearch != nil {
		eventsResp, err := dbSvc.deadlineSearch.SearchEvents(ctx, q, services.EventSearchOptions{
			Jurisdiction: "UPC",
			Limit:        perGroupLimit.events,
		})
		if err == nil && eventsResp != nil {
			resp.Events = eventsResp.Events
		}
	}

	// Scenarios: caller's own scenarios filtered by ILIKE on name.
	// Borrows ListMyScenarios + filters in-memory; the list endpoint
	// already caps at the small per-user fan-out and there's no index
	// on (owner_id, name) yet — in-memory filter is cheap at 10s-of-
	// rows scale.
	if dbSvc != nil && dbSvc.scenarioBuilder != nil {
		scenarios, err := dbSvc.scenarioBuilder.ListMyScenarios(ctx, uid, "active")
		if err == nil {
			needle := strings.ToLower(q)
			hits := []builderSearchScenarioHit{}
			for _, sc := range scenarios {
				if !strings.Contains(strings.ToLower(sc.Name), needle) {
					continue
				}
				hits = append(hits, builderSearchScenarioHit{
					ID:        sc.ID,
					Name:      sc.Name,
					Status:    sc.Status,
					UpdatedAt: sc.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
				})
				if len(hits) >= perGroupLimit.scenarios {
					break
				}
			}
			resp.Scenarios = hits
		}
	}

	// Projects (Akten): visible projects filtered by trigram/ILIKE on
	// title, reference, client_number, matter_number. ProjectService.List
	// already applies team-based RLS via visibilityPredicate.
	if dbSvc != nil && dbSvc.projects != nil {
		projects, err := dbSvc.projects.List(ctx, uid, services.ProjectFilter{
			Search: q,
		})
		if err == nil {
			hits := make([]builderSearchProjectHit, 0, len(projects))
			for _, p := range projects {
				hits = append(hits, builderSearchProjectHit{
					ID:           p.ID,
					Type:         p.Type,
					Title:        p.Title,
					Reference:    p.Reference,
					CaseNumber:   p.CaseNumber,
					MatterNumber: p.MatterNumber,
					ClientNumber: p.ClientNumber,
				})
				if len(hits) >= perGroupLimit.projects {
					break
				}
			}
			resp.Projects = hits
		}
	}

	resp.Counts = builderSearchCounts{
		Events:    len(resp.Events),
		Scenarios: len(resp.Scenarios),
		Projects:  len(resp.Projects),
	}
	writeJSON(w, http.StatusOK, resp)
}

type builderSearchPerGroup struct {
	events    int
	scenarios int
	projects  int
}

// parseBuilderSearchLimit reads ?limit=<n> as a hint for the events
// group (largest expected hit count). Scenarios + projects use smaller
// caps because their drop-down rows are visually heavier. The shared
// caller-supplied bound is interpreted as the events cap; scenarios
// and projects are derived from it.
func parseBuilderSearchLimit(raw string) builderSearchPerGroup {
	def := builderSearchPerGroup{events: 8, scenarios: 5, projects: 5}
	if raw == "" {
		return def
	}
	n, err := strconv.Atoi(raw)
	if err != nil || n <= 0 {
		return def
	}
	if n > 30 {
		n = 30
	}
	return builderSearchPerGroup{
		events:    n,
		scenarios: max(1, n/2),
		projects:  max(1, n/2),
	}
}
