-- 13_inc_search_fn.sql — fleettickets · ticket explorer search read-API
-- ─────────────────────────────────────────────────────────────────────────────
-- reporting.fn_inc_search powers the FleetOps "Ticket explorer" — an ad-hoc query
-- over tickets by id / engineer / cluster / status / state / time, for historical
-- and current tracking ("who closed what, where, when"). Backs GET /webhook/inc-search.
--
-- Source = tickets.inc (accumulates every ticket, never deleted → historical closures
-- AND current open), using the generated columns from migration 03. owner is
-- case-normalized (initcap(lower(...)), like migration 12) so an engineer isn't split
-- across casings. All filters optional and AND-combined; results capped at p_limit.
--
--   state = 'closed' (default) → not-actionable, closed_at within [p_from, p_to)
--                                (bounds optional → all-time)
--           'open'             → currently actionable (time ignored — open = live)
--           'all'              → open OR closed-in-range
--
-- Returns { count, truncated, rows: [...] }. Idempotent (CREATE OR REPLACE).
-- ─────────────────────────────────────────────────────────────────────────────

SET search_path = tickets, public;

CREATE OR REPLACE FUNCTION reporting.fn_inc_search(
  p_ticket_id text        DEFAULT NULL,
  p_owner     text        DEFAULT NULL,
  p_cluster   text        DEFAULT NULL,
  p_status    text        DEFAULT NULL,
  p_state     text        DEFAULT 'closed',
  p_from      timestamptz DEFAULT NULL,
  p_to        timestamptz DEFAULT NULL,
  p_limit     integer     DEFAULT 500
)
 RETURNS jsonb LANGUAGE plpgsql STABLE AS $fn$
DECLARE
  v_state text := lower(COALESCE(NULLIF(p_state, ''), 'closed'));
  v_limit integer := LEAST(GREATEST(COALESCE(p_limit, 500), 1), 5000);
  v_result jsonb;
BEGIN
  p_ticket_id := NULLIF(trim(p_ticket_id), '');
  p_owner     := NULLIF(trim(p_owner), '');
  p_cluster   := NULLIF(p_cluster, '');
  p_status    := NULLIF(p_status, '');

  WITH hits AS (
    SELECT ticket_id, normalized_status, cluster, region, location_name,
           initcap(lower(NULLIF(owner, ''))) AS owner, assigned_team,
           sla_status, mttr, closed_at, created_at_service, is_actionable,
           CASE WHEN geom IS NOT NULL THEN ST_Y(geom) END AS lat,
           CASE WHEN geom IS NOT NULL THEN ST_X(geom) END AS lng
    FROM tickets.inc
    WHERE (p_ticket_id IS NULL OR ticket_id ILIKE '%' || p_ticket_id || '%')
      AND (p_owner     IS NULL OR lower(owner) LIKE '%' || lower(p_owner) || '%')
      AND (p_cluster   IS NULL OR cluster = p_cluster)
      AND (p_status    IS NULL OR normalized_status = p_status)
      AND CASE v_state
            WHEN 'open' THEN COALESCE(is_actionable, false)
            WHEN 'all'  THEN COALESCE(is_actionable, false)
                             OR (closed_at IS NOT NULL
                                 AND (p_from IS NULL OR closed_at >= p_from)
                                 AND (p_to   IS NULL OR closed_at <  p_to))
            ELSE             NOT COALESCE(is_actionable, false)   -- 'closed'
                             AND closed_at IS NOT NULL
                             AND (p_from IS NULL OR closed_at >= p_from)
                             AND (p_to   IS NULL OR closed_at <  p_to)
          END
  ),
  total AS (SELECT count(*) AS n FROM hits),
  page AS (
    SELECT * FROM hits
    ORDER BY closed_at DESC NULLS LAST, created_at_service DESC NULLS LAST
    LIMIT v_limit
  )
  SELECT jsonb_build_object(
    'count',     (SELECT n FROM total),
    'truncated', (SELECT n FROM total) > v_limit,
    'limit',     v_limit,
    'state',     v_state,
    'rows', COALESCE((SELECT jsonb_agg(to_jsonb(page)
                             ORDER BY page.closed_at DESC NULLS LAST,
                                      page.created_at_service DESC NULLS LAST)
                      FROM page), '[]'::jsonb)
  ) INTO v_result;

  RETURN v_result;
END $fn$;

COMMENT ON FUNCTION reporting.fn_inc_search(text, text, text, text, text, timestamptz, timestamptz, integer) IS
  'FleetOps ticket explorer: search tickets.inc by id/owner/cluster/status/state/time '
  '(owner case-normalized). Returns {count, truncated, rows}. fleettickets 13.';

-- grants (guarded: roles may not exist on a fresh DB)
DO $grants$
BEGIN
  IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'dashboard_ro') THEN
    GRANT EXECUTE ON FUNCTION reporting.fn_inc_search(text, text, text, text, text, timestamptz, timestamptz, integer) TO dashboard_ro;
  END IF;
  IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'grafana_ro') THEN
    GRANT EXECUTE ON FUNCTION reporting.fn_inc_search(text, text, text, text, text, timestamptz, timestamptz, integer) TO grafana_ro;
  END IF;
END $grants$;
