-- 02_import_meta.sql — fleettickets · snapshot metadata + map freshness
-- ─────────────────────────────────────────────────────────────────────────────
-- The n8n S3 export now wraps each dataset in a metadata envelope
-- ({ "metadata": {...}, "records": [...] }; see n8n-s3-export-workflows.md).
-- We capture that envelope per dataset at ingest so the map can show how fresh
-- the snapshot is, and re-define reporting.fn_tickets_for_map (same signature —
-- dashboard_api unchanged) to expose it under summary.freshness.
--
-- Idempotent: safe on a fresh DB and re-appliable on the live DB.
-- ─────────────────────────────────────────────────────────────────────────────

SET search_path = tickets, public;

-- ── per-dataset snapshot metadata (one row per dataset; upserted each ingest) ─
CREATE TABLE IF NOT EXISTS tickets.import_meta (
  dataset          text PRIMARY KEY,            -- 'inc' | 'crq'
  export_type      text,                        -- 'delta' | 'full'
  exported_at      timestamptz,                 -- metadata.exported_at (source)
  snapshot_date    date,                        -- metadata.snapshot_date (full runs)
  source_schema    text,
  source_table     text,
  row_count        integer,                     -- metadata.row_count (source count)
  records_ingested integer,                     -- rows we actually read/upserted
  n8n_execution_id text,
  metadata         jsonb,                       -- full envelope metadata (audit)
  ingested_at      timestamptz NOT NULL DEFAULT now()
);

-- ── read function — add summary.freshness (signature unchanged) ──────────────
CREATE OR REPLACE FUNCTION reporting.fn_tickets_for_map(
  p_service_type text    DEFAULT NULL,
  p_status       text    DEFAULT NULL,
  p_open_only    boolean DEFAULT true
)
 RETURNS jsonb LANGUAGE plpgsql STABLE AS $fn$
DECLARE v_result jsonb;
BEGIN
  p_service_type := lower(NULLIF(p_service_type, ''));
  p_status       := NULLIF(p_status, '');
  WITH filtered AS (
    SELECT 'inc'::text AS service_type, raw, geom, geo_source FROM tickets.inc
     WHERE geom IS NOT NULL
       AND (p_service_type IS NULL OR p_service_type = 'inc')
       AND (p_status IS NULL OR raw->>'normalized_status' = p_status)
       AND (NOT p_open_only OR (raw->>'is_actionable')::boolean IS TRUE)
    UNION ALL
    SELECT 'crq'::text AS service_type, raw, geom, geo_source FROM tickets.crq
     WHERE geom IS NOT NULL
       AND (p_service_type IS NULL OR p_service_type = 'crq')
       AND (p_status IS NULL OR raw->>'normalized_status' = p_status)
       AND (NOT p_open_only OR (raw->>'is_actionable')::boolean IS TRUE)
  )
  SELECT jsonb_build_object(
    'summary', jsonb_build_object(
      'ticket_count', COUNT(*),
      'inc',          COUNT(*) FILTER (WHERE service_type = 'inc'),
      'crq',          COUNT(*) FILTER (WHERE service_type = 'crq'),
      'open',         COUNT(*) FILTER (WHERE (raw->>'is_actionable')::boolean IS TRUE),
      'by_status',    (SELECT jsonb_object_agg(s, c)
                         FROM (SELECT raw->>'normalized_status' AS s, COUNT(*) AS c
                                 FROM filtered GROUP BY raw->>'normalized_status') z),
      'freshness',    (SELECT jsonb_object_agg(dataset, jsonb_build_object(
                                'export_type',      export_type,
                                'exported_at',      exported_at,
                                'snapshot_date',    snapshot_date,
                                'row_count',        row_count,
                                'records_ingested', records_ingested,
                                'ingested_at',      ingested_at))
                         FROM tickets.import_meta
                        WHERE p_service_type IS NULL OR dataset = p_service_type)
    ),
    'geojson', jsonb_build_object(
      'type', 'FeatureCollection',
      'features', COALESCE(jsonb_agg(
        jsonb_build_object(
          'type', 'Feature',
          'properties', jsonb_build_object(
            'ticket_id',     raw->>'ticket_id',
            'service_type',  service_type,
            'status',        raw->>'normalized_status',
            'raw_status',    raw->>'raw_status',
            'cluster',       raw->>'cluster',
            'region',        raw->>'region',
            'location_name', raw->>'location_name',
            'department',    raw->>'department',
            'owner',         raw->>'owner',
            'assigned_team', raw->>'assigned_team',
            'sla_status',    raw->>'sla_status',
            'is_actionable', (raw->>'is_actionable')::boolean,
            'geo_source',    geo_source,
            'created_at',    raw->>'created_at_service',
            'scheduled_at',  raw->>'scheduled_at'
          ),
          'geometry', ST_AsGeoJSON(geom)::jsonb
        )
      ), '[]'::jsonb)
    )
  ) INTO v_result FROM filtered;
  RETURN v_result;
END $fn$;

COMMENT ON FUNCTION reporting.fn_tickets_for_map(text, text, boolean) IS
  'INC/CRQ tickets (tickets.inc + tickets.crq, raw-jsonb-first) as GeoJSON, with '
  'summary.freshness from tickets.import_meta. fleettickets 02.';

-- ── grants (guarded: roles may not exist on a fresh DB) ───────────────────────
DO $grants$
BEGIN
  IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'tracksolid_owner') THEN
    GRANT SELECT, INSERT, UPDATE, DELETE ON tickets.import_meta TO tracksolid_owner;
  END IF;
  IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'dashboard_ro') THEN
    GRANT SELECT ON tickets.import_meta TO dashboard_ro;
  END IF;
  IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'grafana_ro') THEN
    GRANT SELECT ON tickets.import_meta TO grafana_ro;
  END IF;
END $grants$;
