-- Migration 120: conservative form_factor recovery (provably-wrong rows only)
-- ─────────────────────────────────────────────────────────────────────────────
-- Problem: some scrapers default form_factor to 'SFP'/'SFP+', so a 400G QSFP-DD
-- DAC ends up tagged 'SFP+'. We only ever correct a form_factor that is
-- PROVABLY wrong — i.e. its max electrical speed (form_factors.max_speed_gbps)
-- is below the row's actual speed_gbps — and only when the part_number carries
-- an explicit form-factor token that CAN carry that speed. Opaque part numbers
-- (e.g. 'EOLS-1303-40-D') are left untouched; we never guess a form factor.
--
-- The scraped original is preserved in form_factor_raw for audit/rollback.
-- Ingest-time self-healing is added to the tip_transceivers_biu() trigger.
-- Idempotent.
-- ─────────────────────────────────────────────────────────────────────────────

-- 1. Preserve the scraped original ---------------------------------------------
ALTER TABLE transceivers ADD COLUMN IF NOT EXISTS form_factor_raw TEXT;
COMMENT ON COLUMN transceivers.form_factor_raw IS
  'Form factor exactly as scraped, before tip_recover_form_factor() correction. Audit/rollback only.';

UPDATE transceivers SET form_factor_raw = form_factor
 WHERE form_factor_raw IS NULL;

-- 2. Explicit form-factor token extraction (most-specific first) ----------------
CREATE OR REPLACE FUNCTION tip_extract_ff_token(p_part_number TEXT) RETURNS TEXT
LANGUAGE sql IMMUTABLE PARALLEL SAFE AS $$
  SELECT CASE
    WHEN p_part_number ~* 'qsfp-?dd-?800|qdd-?800'    THEN 'QSFP-DD800'
    -- Q56DD / QSFP56-DD is the 400G double-density QSFP connector (Dell naming) → QSFP-DD
    WHEN p_part_number ~* 'q56dd|qsfp56-?dd'          THEN 'QSFP-DD'
    WHEN p_part_number ~* 'osfp-?xd'                   THEN 'OSFP-XD'
    WHEN p_part_number ~* 'osfp224'                    THEN 'OSFP224'
    WHEN p_part_number ~* 'osfp112'                    THEN 'OSFP112'
    WHEN p_part_number ~* '\mosfp\M'                   THEN 'OSFP'
    WHEN p_part_number ~* 'qsfp112'                    THEN 'QSFP112'
    WHEN p_part_number ~* 'qsfp-?dd|\mqdd\M|qsfpdd'    THEN 'QSFP-DD'
    WHEN p_part_number ~* 'qsfp56|\mq56\M'             THEN 'QSFP56'
    WHEN p_part_number ~* 'qsfp28|\mq28\M'             THEN 'QSFP28'
    WHEN p_part_number ~* 'qsfp\+|qsfp-?plus'          THEN 'QSFP+'
    WHEN p_part_number ~* 'sfp-?dd|sfpdd'              THEN 'SFP-DD'
    WHEN p_part_number ~* 'sfp112'                     THEN 'SFP112'
    WHEN p_part_number ~* 'sfp56'                      THEN 'SFP56'
    WHEN p_part_number ~* 'sfp28'                      THEN 'SFP28'
    WHEN p_part_number ~* 'sfp\+|sfp-?plus'            THEN 'SFP+'
    WHEN p_part_number ~* 'cfp2-?dco'                  THEN 'CFP2-DCO'
    WHEN p_part_number ~* 'cfp2'                       THEN 'CFP2'
    WHEN p_part_number ~* 'cfp8'                       THEN 'CFP8'
    WHEN p_part_number ~* 'cfp4'                       THEN 'CFP4'
    WHEN p_part_number ~* '\mcfp\M'                    THEN 'CFP'
    WHEN p_part_number ~* '\mxfp\M'                    THEN 'XFP'
    ELSE NULL
  END;
$$;

-- 3. Recovery: return corrected FF only when the current one is PROVABLY wrong ---
CREATE OR REPLACE FUNCTION tip_recover_form_factor(
  p_part_number TEXT, p_form_factor TEXT, p_speed_gbps NUMERIC
) RETURNS TEXT
LANGUAGE plpgsql STABLE PARALLEL SAFE AS $$
DECLARE
  v_token   TEXT := tip_extract_ff_token(p_part_number);
  v_cur_max NUMERIC;
  v_tok_max NUMERIC;
BEGIN
  IF v_token IS NULL OR p_form_factor IS NULL OR p_speed_gbps IS NULL THEN
    RETURN p_form_factor;
  END IF;
  IF upper(v_token) = upper(p_form_factor) THEN
    RETURN p_form_factor;                       -- already correct
  END IF;
  SELECT max_speed_gbps INTO v_cur_max FROM form_factors WHERE upper(name) = upper(p_form_factor);
  SELECT max_speed_gbps INTO v_tok_max FROM form_factors WHERE name = v_token;
  -- current provably wrong for the speed, and the token can actually carry it
  IF v_cur_max IS NOT NULL AND v_tok_max IS NOT NULL
     AND p_speed_gbps > v_cur_max AND v_tok_max >= p_speed_gbps THEN
    RETURN v_token;
  END IF;
  RETURN p_form_factor;                          -- not confident → leave as-is
END;
$$;

-- 4. Backfill the confidently-recoverable rows ---------------------------------
UPDATE transceivers
   SET form_factor = tip_recover_form_factor(part_number, form_factor, speed_gbps)
 WHERE form_factor IS DISTINCT FROM tip_recover_form_factor(part_number, form_factor, speed_gbps);

-- 5. Extend the ingest trigger with FF self-healing ----------------------------
CREATE OR REPLACE FUNCTION tip_transceivers_biu() RETURNS TRIGGER
LANGUAGE plpgsql AS $$
BEGIN
  -- product_type: recompute only when a classification input changed
  IF TG_OP = 'INSERT'
     OR NEW.product_type IS NULL
     OR NEW.part_number IS DISTINCT FROM OLD.part_number
     OR NEW.category    IS DISTINCT FROM OLD.category
     OR NEW.reach_label IS DISTINCT FROM OLD.reach_label
  THEN
    NEW.product_type := tip_classify_product_type(NEW.part_number, NEW.category, NEW.reach_label);
  END IF;

  -- form_factor: preserve the scraped original, then correct only provably-wrong values
  NEW.form_factor_raw := COALESCE(NEW.form_factor_raw, NEW.form_factor);
  NEW.form_factor     := tip_recover_form_factor(NEW.part_number, NEW.form_factor, NEW.speed_gbps);

  RETURN NEW;
END;
$$;

-- (trigger trg_transceivers_biu already bound in migration 119 — replacing the
--  function body above is sufficient.)

INSERT INTO _migrations (filename)
VALUES ('120-form-factor-recovery.sql')
ON CONFLICT (filename) DO NOTHING;

-- 6. Report --------------------------------------------------------------------
DO $$
DECLARE v_fixed INT; v_left INT;
BEGIN
  SELECT count(*) INTO v_fixed FROM transceivers WHERE form_factor IS DISTINCT FROM form_factor_raw;
  SELECT count(*) INTO v_left FROM transceivers t JOIN form_factors f ON upper(f.name)=upper(t.form_factor)
    WHERE t.speed_gbps > f.max_speed_gbps;
  RAISE NOTICE 'form_factor corrected on % rows; % provably-wrong rows left untouched (opaque, not guessed).', v_fixed, v_left;
END;
$$;
