-- Risk flags (red flags) for procurement transparency, based on OCP indicators.
-- Idempotent: safe to re-run.
BEGIN;

-- ── Column on announcements ──
ALTER TABLE seap.announcements
  ADD COLUMN IF NOT EXISTS risk_flags JSONB;

CREATE INDEX IF NOT EXISTS idx_ann_risk_flags
  ON seap.announcements USING gin(risk_flags)
  WHERE risk_flags IS NOT NULL AND jsonb_array_length(risk_flags) > 0;


-- ── Materialized view: per-CPV-division median awarded value ──
DROP MATERIALIZED VIEW IF EXISTS seap.mv_cpv_median_value CASCADE;
CREATE MATERIALIZED VIEW seap.mv_cpv_median_value AS
SELECT
    cpv_division,
    count(*)::int AS contracts,
    percentile_cont(0.5) WITHIN GROUP (ORDER BY awarded_value)::numeric(15,2) AS median_value,
    avg(awarded_value)::numeric(15,2) AS avg_value,
    percentile_cont(0.95) WITHIN GROUP (ORDER BY awarded_value)::numeric(15,2) AS p95_value
FROM seap.announcements
WHERE awarded_value IS NOT NULL
  AND awarded_value > 0
  AND cpv_division IS NOT NULL
GROUP BY cpv_division
HAVING count(*) >= 5;

CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_cpv_median_pk
  ON seap.mv_cpv_median_value(cpv_division);


-- ── Materialized view: authority supplier concentration (top supplier % of yearly value) ──
DROP MATERIALIZED VIEW IF EXISTS seap.mv_authority_concentration CASCADE;
CREATE MATERIALIZED VIEW seap.mv_authority_concentration AS
WITH yearly_pairs AS (
  SELECT
    a.authority_cui,
    MIN(a.authority_name) AS authority_name,
    EXTRACT(YEAR FROM a.publication_date)::int AS year,
    a.supplier_cui,
    MIN(a.supplier_name) AS supplier_name,
    SUM(a.awarded_value)::numeric(15,2) AS total_value,
    COUNT(*)::int AS contracts
  FROM seap.announcements a
  WHERE a.authority_cui IS NOT NULL
    AND a.supplier_cui IS NOT NULL
    AND a.awarded_value IS NOT NULL
    AND a.awarded_value > 0
    AND a.publication_date IS NOT NULL
    AND a.publication_date >= now() - interval '36 months'
  GROUP BY a.authority_cui, EXTRACT(YEAR FROM a.publication_date), a.supplier_cui
),
yearly_totals AS (
  SELECT
    authority_cui,
    year,
    SUM(total_value) AS year_total,
    SUM(contracts) AS year_contracts
  FROM yearly_pairs
  GROUP BY authority_cui, year
),
ranked AS (
  SELECT
    p.authority_cui,
    p.authority_name,
    p.year,
    p.supplier_cui,
    p.supplier_name,
    p.total_value,
    p.contracts,
    t.year_total,
    t.year_contracts,
    ROW_NUMBER() OVER (PARTITION BY p.authority_cui, p.year ORDER BY p.total_value DESC) AS rn,
    (p.total_value / NULLIF(t.year_total, 0))::numeric(6,4) AS share
  FROM yearly_pairs p
  JOIN yearly_totals t USING (authority_cui, year)
)
SELECT
  authority_cui,
  authority_name,
  year,
  supplier_cui  AS top_supplier_cui,
  supplier_name AS top_supplier_name,
  total_value   AS top_supplier_value,
  contracts     AS top_supplier_contracts,
  year_total,
  year_contracts,
  share AS top_supplier_share
FROM ranked
WHERE rn = 1
  AND year_total >= 100000;     -- skip tiny totals (noise)

CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_auth_conc_pk
  ON seap.mv_authority_concentration(authority_cui, year);
CREATE INDEX IF NOT EXISTS idx_mv_auth_conc_share
  ON seap.mv_authority_concentration(top_supplier_share DESC NULLS LAST);


-- ── View: single-bidder contracts ──
DROP VIEW IF EXISTS seap.v_single_bidder CASCADE;
CREATE VIEW seap.v_single_bidder AS
SELECT a.*
FROM seap.announcements a
WHERE a.type = 'ca_notice'
  AND (
    a.num_offers = 1
    OR (
      a.details IS NOT NULL
      AND jsonb_typeof(a.details->'all_winners') = 'array'
      AND jsonb_array_length(a.details->'all_winners') = 1
    )
  );


-- ── Function: compute risk flags for a single announcement ──
-- Returns JSONB array of { code, severity, label, detail? }
CREATE OR REPLACE FUNCTION seap.compute_announcement_flags(
  p_id BIGINT
) RETURNS JSONB
LANGUAGE plpgsql
AS $$
DECLARE
  rec      RECORD;
  flags    JSONB := '[]'::jsonb;
  v_median NUMERIC;
BEGIN
  SELECT a.id, a.type, a.publication_date, a.deadline_submission,
         a.awarded_value, a.estimated_value, a.cpv_division,
         a.num_offers, a.details
    INTO rec
    FROM seap.announcements a WHERE a.id = p_id;

  IF NOT FOUND THEN RETURN NULL; END IF;

  -- 1) Single bidder (only meaningful for ca_notice with winner data)
  IF rec.type = 'ca_notice' THEN
    IF rec.num_offers = 1 THEN
      flags := flags || jsonb_build_object(
        'code',     'single_bidder',
        'severity', 'high',
        'label',    'Un singur ofertant'
      );
    ELSIF rec.details IS NOT NULL
       AND jsonb_typeof(rec.details->'all_winners') = 'array'
       AND jsonb_array_length(rec.details->'all_winners') = 1 THEN
      flags := flags || jsonb_build_object(
        'code',     'single_bidder',
        'severity', 'high',
        'label',    'Un singur câștigător'
      );
    END IF;
  END IF;

  -- 2) Short deadline (only c_notice / rfq_invitation have submission deadlines)
  IF rec.type IN ('c_notice','rfq_invitation')
     AND rec.publication_date IS NOT NULL
     AND rec.deadline_submission IS NOT NULL
     AND (rec.deadline_submission - rec.publication_date) < interval '10 days' THEN
    flags := flags || jsonb_build_object(
      'code',     'short_deadline',
      'severity', 'medium',
      'label',    'Termen scurt',
      'detail',   EXTRACT(EPOCH FROM (rec.deadline_submission - rec.publication_date))/86400.0
    );
  END IF;

  -- 3) Suspicious savings: awarded_value < 50% of estimated
  IF rec.awarded_value IS NOT NULL
     AND rec.estimated_value IS NOT NULL
     AND rec.awarded_value > 0
     AND rec.estimated_value > 0
     AND rec.awarded_value < 0.5 * rec.estimated_value THEN
    flags := flags || jsonb_build_object(
      'code',     'suspicious_savings',
      'severity', 'medium',
      'label',    'Economii suspecte',
      'detail',   round(100.0 * (1 - rec.awarded_value / rec.estimated_value))::int
    );
  END IF;

  -- 5) Overprice: awarded_value > 2 * median per CPV division
  IF rec.awarded_value IS NOT NULL
     AND rec.awarded_value > 0
     AND rec.cpv_division IS NOT NULL THEN
    SELECT median_value INTO v_median
      FROM seap.mv_cpv_median_value
      WHERE cpv_division = rec.cpv_division;
    IF v_median IS NOT NULL AND v_median > 0
       AND rec.awarded_value > 2 * v_median THEN
      flags := flags || jsonb_build_object(
        'code',     'overprice',
        'severity', 'medium',
        'label',    'Peste piață',
        'detail',   round((rec.awarded_value / v_median)::numeric, 1)
      );
    END IF;
  END IF;

  RETURN flags;
END;
$$;


-- ── Function: refresh all risk-related materialized views ──
CREATE OR REPLACE FUNCTION seap.refresh_risk_views()
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
  REFRESH MATERIALIZED VIEW CONCURRENTLY seap.mv_cpv_median_value;
EXCEPTION WHEN OTHERS THEN
  REFRESH MATERIALIZED VIEW seap.mv_cpv_median_value;
END;
$$;

CREATE OR REPLACE FUNCTION seap.refresh_concentration()
RETURNS VOID
LANGUAGE plpgsql
AS $$
BEGIN
  REFRESH MATERIALIZED VIEW CONCURRENTLY seap.mv_authority_concentration;
EXCEPTION WHEN OTHERS THEN
  REFRESH MATERIALIZED VIEW seap.mv_authority_concentration;
END;
$$;

COMMIT;

-- Initial population (non-transactional)
REFRESH MATERIALIZED VIEW seap.mv_cpv_median_value;
REFRESH MATERIALIZED VIEW seap.mv_authority_concentration;
