/-
  ============================================================
  SDP CERTIFICATE VERIFICATION ENGINE

  Sparse polynomial arithmetic over ℚ for verifying
  SOS (Sum-of-Squares) certificates produced by external
  SDP solvers.

  Architecture:
  - External SDP solver (Python shim) → rational coefficients
  - This module: expand Σ qᵢ² + Σ sⱼgⱼ, compare with target p
  - Verification is FINITE: polynomial arithmetic + coefficient check
  - native_decide-compatible: all operations are decidable

  Pipeline:
    sdp_sos_solver.py → GoormaghtighCert.lean (data) → here (verify)
  ============================================================
-/

import Mathlib.Data.Rat.Defs
import Mathlib.Data.Fintype.Basic
import Mathlib.Tactic

namespace Semantics.SDPVerify

-- ============================================================
-- §0  SPARSE POLYNOMIAL REPRESENTATION
-- ============================================================

/-- A monomial exponent vector over n variables.
    Example: x₀²x₂³ in 4 variables → fun i => [2, 0, 3, 0][i] -/
abbrev Exponent (n : ℕ) := Fin n → ℕ

/-- A term is a (coefficient, exponent) pair. -/
structure Term (n : ℕ) where
  coeff : ℚ
  expon : Exponent n
  deriving Repr

/-- A sparse polynomial: a list of terms.
    Invariant: no duplicate exponents after normalization.
    Example: 3x₀² + 2x₁ = [(3, [2,0,0,0]), (2, [0,1,0,0])] -/
abbrev SparsePoly (n : ℕ) := List (Term n)

instance : Inhabited (SparsePoly n) := ⟨[]⟩

-- ============================================================
-- §1  EXPONENT OPERATIONS
-- ============================================================

section ExponentOps

/-- Decidable equality for exponent vectors. -/
instance : DecidableEq (Exponent n) := inferInstance

/-- Add two exponent vectors (monomial multiplication).
    x₀²x₁ · x₁x₂ = x₀²x₁²x₂ → [2,1,0,0] + [0,1,1,0] = [2,2,1,0] -/
def exponentAdd {n : ℕ} (a b : Exponent n) : Exponent n :=
  fun i => a i + b i

/-- Zero exponent (constant monomial). -/
def exponentZero (n : ℕ) : Exponent n := fun _ => 0

/-- Compare exponents lexicographically (for sorting/normalization).
    Returns true if a < b in lex order. -/
def exponentLt {n : ℕ} (a b : Exponent n) : Bool :=
  let rec go (i : ℕ) : Bool :=
    if h : i < n then
      if a ⟨i, h⟩ < b ⟨i, h⟩ then true
      else if a ⟨i, h⟩ > b ⟨i, h⟩ then false
      else go (i + 1)
    else false
  go 0

/-- Check exponent equality. -/
def exponentEq {n : ℕ} (a b : Exponent n) : Bool :=
  (List.range n).all fun i =>
    match Nat.decLt i n with
    | .isTrue h => a ⟨i, h⟩ == b ⟨i, h⟩
    | .isFalse _ => true

end ExponentOps

-- ============================================================
-- §2  POLYNOMIAL ARITHMETIC
-- ============================================================

section PolyArith

/-- The zero polynomial. -/
def zeroPoly (n : ℕ) : SparsePoly n := []

/-- A constant polynomial. -/
def constPoly {n : ℕ} (c : ℚ) : SparsePoly n :=
  if c == 0 then [] else [⟨c, exponentZero n⟩]

/-- A single-variable monomial: c · xᵢᵏ -/
def monomialPoly {n : ℕ} (c : ℚ) (var : Fin n) (deg : ℕ) : SparsePoly n :=
  if c == 0 then []
  else [⟨c, fun j => if j == var then deg else 0⟩]

/-- Add two sparse polynomials (concatenate terms, normalize later). -/
def addPoly {n : ℕ} (p q : SparsePoly n) : SparsePoly n := p ++ q

/-- Scale a polynomial by a rational constant. -/
def scalePoly {n : ℕ} (c : ℚ) (p : SparsePoly n) : SparsePoly n :=
  p.map fun t => ⟨c * t.coeff, t.expon⟩

/-- Negate a polynomial. -/
def negPoly {n : ℕ} (p : SparsePoly n) : SparsePoly n :=
  p.map fun t => ⟨-t.coeff, t.expon⟩

/-- Multiply a single term by a polynomial. -/
def mulTermPoly {n : ℕ} (t : Term n) (p : SparsePoly n) : SparsePoly n :=
  p.map fun s => ⟨t.coeff * s.coeff, exponentAdd t.expon s.expon⟩

/-- Multiply two sparse polynomials (distribute and collect). -/
def mulPoly {n : ℕ} (p q : SparsePoly n) : SparsePoly n :=
  p.foldl (fun acc t => acc ++ mulTermPoly t q) []

/-- Square a polynomial: p² = p · p. -/
def sqPoly {n : ℕ} (p : SparsePoly n) : SparsePoly n :=
  mulPoly p p

/-- Sum of squares: Σᵢ qᵢ². -/
def sumSqPoly {n : ℕ} (qs : List (SparsePoly n)) : SparsePoly n :=
  qs.foldl (fun acc q => addPoly acc (sqPoly q)) (zeroPoly n)

/-- Weighted sum: Σⱼ sⱼ · gⱼ. -/
def weightedSumPoly {n : ℕ} (pairs : List (SparsePoly n × SparsePoly n)) : SparsePoly n :=
  pairs.foldl (fun acc (s, g) => addPoly acc (mulPoly s g)) (zeroPoly n)

end PolyArith

-- ============================================================
-- §3  NORMALIZATION (collect like terms, sort, drop zeros)
-- ============================================================

section Normalize

/-- Insert a term into a sorted (by exponent) accumulator,
    merging coefficients if the exponent already exists. -/
def insertTerm {n : ℕ} (t : Term n) (acc : List (Term n)) : List (Term n) :=
  match acc with
  | [] => [t]
  | h :: rest =>
    if t.expon == h.expon then
      ⟨t.coeff + h.coeff, h.expon⟩ :: rest
    else if exponentLt t.expon h.expon then
      t :: h :: rest
    else
      h :: insertTerm t rest

/-- Normalize a polynomial: collect like terms, sort by exponent, drop zeros.
    After normalization, no two terms share the same exponent. -/
def normalizePoly {n : ℕ} (p : SparsePoly n) : SparsePoly n :=
  let sorted := p.foldl (fun acc t => insertTerm t acc) []
  sorted.filter fun t => t.coeff != 0

end Normalize

-- ============================================================
-- §4  COEFFICIENT-WISE EQUALITY
-- ============================================================

section Equality

/-- Check if two normalized polynomials are equal (same terms in order). -/
def polyEqNormalized {n : ℕ} (p q : List (Term n)) : Bool :=
  match p, q with
  | [], [] => true
  | [], _ => false
  | _, [] => false
  | hp :: rp, hq :: rq =>
    hp.expon == hq.expon &&
    hp.coeff == hq.coeff &&
    polyEqNormalized rp rq

/-- Check if two sparse polynomials are equal
    (normalize both, then compare). -/
def polyEq {n : ℕ} (p q : SparsePoly n) : Bool :=
  polyEqNormalized (normalizePoly p) (normalizePoly q)

/-- Check if a polynomial is zero (all coefficients vanish). -/
def polyIsZero {n : ℕ} (p : SparsePoly n) : Bool :=
  (normalizePoly p).isEmpty

end Equality

-- ============================================================
-- §5  CERTIFICATE VERIFICATION
-- ============================================================

section Verify

/-- An SDP certificate: the output of the SDP solver, rationalized.
    Contains:
    - sos_components: the SOS polynomials qᵢ (each as SparsePoly)
    - weighted_pairs: the weighted SOS pairs (sⱼ, gⱼ)
    - target: the polynomial p to verify against

    The certificate is VALID iff:
      normalize(Σ qᵢ² + Σ sⱼ·gⱼ) = normalize(p) -/
structure SDPCertificate (n : ℕ) where
  sos_components : List (SparsePoly n)
  weighted_pairs : List (SparsePoly n × SparsePoly n)
  target         : SparsePoly n
  degree         : ℕ
  level          : ℕ

/-- Verify an SDP certificate: expand the SOS representation and
    check coefficient-wise equality with the target polynomial.

    This is the KEY function. It replaces the sorry in PutinarBackbone.
    The computation is FINITE and EXACT (rational arithmetic).

    Returns true iff: Σ qᵢ² + Σ sⱼ·gⱼ = p (coefficient-wise). -/
def verifyCertificate {n : ℕ} (cert : SDPCertificate n) : Bool :=
  let sosSum := sumSqPoly cert.sos_components
  let weightedSum := weightedSumPoly cert.weighted_pairs
  let lhs := addPoly sosSum weightedSum
  polyEq lhs cert.target

/-- Verify that all SOS components are well-formed:
    each has finite degree and rational coefficients. -/
def verifyCertificateWellFormed {n : ℕ} (cert : SDPCertificate n) : Bool :=
  -- Check that the SOS components are non-empty
  !cert.sos_components.isEmpty &&
  -- Check that all coefficients are finite (not NaN — always true for ℚ)
  true

end Verify

-- ============================================================
-- §6  SOUNDNESS
-- ============================================================

section Soundness

/-- Evaluate a single term at point x. -/
def evalTerm {n : ℕ} (t : Term n) (x : Fin n → ℝ) : ℝ :=
  (t.coeff : ℝ) * Finset.univ.prod (fun i : Fin n => x i ^ t.expon i)

/-- Evaluate a sparse polynomial at a point (semantic evaluation).
    Maps the algebraic representation to the semantic value. -/
noncomputable def evalSparsePoly {n : ℕ} (p : SparsePoly n) (x : Fin n → ℝ) : ℝ :=
  p.foldl (fun acc t => acc + evalTerm t x) 0

/-- Pure arithmetic lemma: foldl + a distributes.
    Proved by induction without touching evalSparsePoly, so no loop risk. -/
lemma foldl_add {n : ℕ} (a : ℝ) (p : SparsePoly n) (x : Fin n → ℝ) :
    p.foldl (fun acc t => acc + evalTerm t x) a = a + p.foldl (fun acc t => acc + evalTerm t x) 0 := by
  induction p generalizing a with
  | nil => simp
  | cons t ts ih =>
    calc
      (t :: ts).foldl (fun acc t => acc + evalTerm t x) a
          = ts.foldl (fun acc t => acc + evalTerm t x) (a + evalTerm t x) := rfl
      _ = (a + evalTerm t x) + ts.foldl (fun acc t => acc + evalTerm t x) 0 := by rw [ih (a + evalTerm t x)]
      _ = a + (evalTerm t x + ts.foldl (fun acc t => acc + evalTerm t x) 0) := by ring
      _ = a + ((t :: ts).foldl (fun acc t => acc + evalTerm t x) 0) := by
        have h : evalTerm t x + ts.foldl (fun acc t => acc + evalTerm t x) 0
            = (t :: ts).foldl (fun acc t => acc + evalTerm t x) 0 := by
          calc
            evalTerm t x + ts.foldl (fun acc t => acc + evalTerm t x) 0
                = ts.foldl (fun acc t => acc + evalTerm t x) (evalTerm t x) := by rw [ih (evalTerm t x)]
            _ = (t :: ts).foldl (fun acc t => acc + evalTerm t x) 0 := by simp
        rw [h]

lemma evalSparsePoly_foldl_add {n : ℕ} (a : ℝ) (p : SparsePoly n) (x : Fin n → ℝ) :
    p.foldl (fun acc t => acc + evalTerm t x) a = a + evalSparsePoly p x := by
  rw [foldl_add, evalSparsePoly]

lemma eval_cons {n : ℕ} (t : Term n) (ts : SparsePoly n) (x : Fin n → ℝ) :
    evalSparsePoly (t :: ts) x = evalTerm t x + evalSparsePoly ts x := by
  calc
    evalSparsePoly (t :: ts) x = (t :: ts).foldl (fun acc t => acc + evalTerm t x) 0 := rfl
    _ = ts.foldl (fun acc t => acc + evalTerm t x) (evalTerm t x) := by simp
    _ = evalTerm t x + evalSparsePoly ts x := by rw [evalSparsePoly_foldl_add]

lemma evalSparsePoly_append {n : ℕ} (p q : SparsePoly n) (x : Fin n → ℝ) :
    evalSparsePoly (p ++ q) x = evalSparsePoly p x + evalSparsePoly q x := by
  induction p generalizing q with
  | nil => simp [evalSparsePoly]
  | cons t ts ih =>
    calc
      evalSparsePoly ((t :: ts) ++ q) x
          = evalSparsePoly (t :: (ts ++ q)) x := by simp
      _ = evalTerm t x + evalSparsePoly (ts ++ q) x := by rw [eval_cons]
      _ = evalTerm t x + (evalSparsePoly ts x + evalSparsePoly q x) := by rw [ih]
      _ = (evalTerm t x + evalSparsePoly ts x) + evalSparsePoly q x := by ring
      _ = evalSparsePoly (t :: ts) x + evalSparsePoly q x := by rw [eval_cons]

lemma evalTerm_exponentAdd {n : ℕ} (t s : Term n) (x : Fin n → ℝ) :
    evalTerm { coeff := t.coeff * s.coeff, expon := exponentAdd t.expon s.expon } x = evalTerm t x * evalTerm s x := by
  dsimp [evalTerm, exponentAdd]
  calc
    ((t.coeff * s.coeff : ℚ) : ℝ) * ∏ i : Fin n, x i ^ (t.expon i + s.expon i)
        = ((t.coeff : ℚ) : ℝ) * ((s.coeff : ℚ) : ℝ) * ∏ i : Fin n, (x i ^ t.expon i * x i ^ s.expon i) := by
      simp [mul_assoc, pow_add]
    _ = ((t.coeff : ℚ) : ℝ) * ((s.coeff : ℚ) : ℝ) * ((∏ i : Fin n, x i ^ t.expon i) * (∏ i : Fin n, x i ^ s.expon i)) := by
      rw [Finset.prod_mul_distrib]
    _ = ((t.coeff : ℚ) : ℝ) * (∏ i : Fin n, x i ^ t.expon i) * (((s.coeff : ℚ) : ℝ) * (∏ i : Fin n, x i ^ s.expon i)) := by ring
    _ = evalTerm t x * evalTerm s x := rfl

lemma eval_mulTermPoly {n : ℕ} (t : Term n) (q : SparsePoly n) (x : Fin n → ℝ) :
    evalSparsePoly (mulTermPoly t q) x = evalTerm t x * evalSparsePoly q x := by
  induction q with
  | nil => simp [evalSparsePoly, mulTermPoly]
  | cons s ss ih =>
    calc
      evalSparsePoly (mulTermPoly t (s :: ss)) x
          = evalSparsePoly ([⟨t.coeff * s.coeff, exponentAdd t.expon s.expon⟩] ++ mulTermPoly t ss) x := by
        simp [mulTermPoly]
      _ = evalSparsePoly [⟨t.coeff * s.coeff, exponentAdd t.expon s.expon⟩] x + evalSparsePoly (mulTermPoly t ss) x := by
        rw [evalSparsePoly_append]
      _ = (evalTerm t x * evalTerm s x) + (evalTerm t x * evalSparsePoly ss x) := by
        have h_single : evalSparsePoly [⟨t.coeff * s.coeff, exponentAdd t.expon s.expon⟩] x
            = evalTerm ⟨t.coeff * s.coeff, exponentAdd t.expon s.expon⟩ x := by
          simp [evalSparsePoly]
        rw [h_single, ih, evalTerm_exponentAdd t s x]
      _ = evalTerm t x * (evalTerm s x + evalSparsePoly ss x) := by ring
      _ = evalTerm t x * evalSparsePoly (s :: ss) x := by rw [eval_cons]

lemma eval_mul {n : ℕ} (p q : SparsePoly n) (x : Fin n → ℝ) :
    evalSparsePoly (mulPoly p q) x = evalSparsePoly p x * evalSparsePoly q x := by
  induction p generalizing q with
  | nil => simp [evalSparsePoly, mulPoly]
  | cons t ts ih =>
    calc
      evalSparsePoly (mulPoly (t :: ts) q) x
          = evalSparsePoly (mulTermPoly t q ++ mulPoly ts q) x := by
        simp [mulPoly, List.foldl]
      _ = evalSparsePoly (mulTermPoly t q) x + evalSparsePoly (mulPoly ts q) x := by
        rw [evalSparsePoly_append]
      _ = (evalTerm t x * evalSparsePoly q x) + (evalSparsePoly ts x * evalSparsePoly q x) := by
        simp [eval_mulTermPoly, ih]
      _ = (evalTerm t x + evalSparsePoly ts x) * evalSparsePoly q x := by ring
      _ = evalSparsePoly (t :: ts) x * evalSparsePoly q x := by rw [eval_cons]

lemma eval_sqPoly {n : ℕ} (q : SparsePoly n) (x : Fin n → ℝ) :
    evalSparsePoly (sqPoly q) x = (evalSparsePoly q x)^2 := by
  rw [sqPoly, eval_mul]; ring

lemma sumSqPoly_foldl_add {n : ℕ} (acc : SparsePoly n) (qs : List (SparsePoly n)) :
    qs.foldl (fun acc' q => addPoly acc' (sqPoly q)) acc = addPoly acc (sumSqPoly qs) := by
  induction qs generalizing acc with
  | nil => simp [sumSqPoly, zeroPoly, addPoly]
  | cons q qs ih =>
    simp [sumSqPoly, List.foldl, addPoly, ih, List.append_assoc, zeroPoly]

lemma eval_sumSqPoly {n : ℕ} (qs : List (SparsePoly n)) (x : Fin n → ℝ) :
    evalSparsePoly (sumSqPoly qs) x = (qs.map (fun q => (evalSparsePoly q x)^2)).sum := by
  induction qs with
  | nil => simp [evalSparsePoly, sumSqPoly, zeroPoly]
  | cons q qs ih =>
    calc
      evalSparsePoly (sumSqPoly (q :: qs)) x
          = evalSparsePoly (qs.foldl (fun acc' q => addPoly acc' (sqPoly q)) (sqPoly q)) x := by
        simp [sumSqPoly, addPoly, List.foldl, zeroPoly]
      _ = evalSparsePoly (addPoly (sqPoly q) (sumSqPoly qs)) x := by
        rw [sumSqPoly_foldl_add (sqPoly q) qs]
      _ = evalSparsePoly (sqPoly q ++ sumSqPoly qs) x := by rw [addPoly]
      _ = evalSparsePoly (sqPoly q) x + evalSparsePoly (sumSqPoly qs) x := by rw [evalSparsePoly_append]
      _ = (evalSparsePoly q x)^2 + (qs.map (fun q => (evalSparsePoly q x)^2)).sum := by
        simp [eval_sqPoly, ih]
      _ = ((q :: qs).map (fun q => (evalSparsePoly q x)^2)).sum := by simp

lemma weightedSumPoly_foldl_add {n : ℕ} (acc : SparsePoly n) (pairs : List (SparsePoly n × SparsePoly n)) :
    pairs.foldl (fun acc' (s, g) => addPoly acc' (mulPoly s g)) acc = addPoly acc (weightedSumPoly pairs) := by
  induction pairs generalizing acc with
  | nil => simp [weightedSumPoly, zeroPoly, addPoly]
  | cons pair pairs ih =>
    rcases pair with ⟨s, g⟩
    simp [weightedSumPoly, List.foldl, addPoly, ih, List.append_assoc, zeroPoly]

lemma eval_weightedSumPoly {n : ℕ} (pairs : List (SparsePoly n × SparsePoly n)) (x : Fin n → ℝ) :
    evalSparsePoly (weightedSumPoly pairs) x =
    (pairs.map (fun (s, g) => evalSparsePoly s x * evalSparsePoly g x)).sum := by
  induction pairs with
  | nil => simp [evalSparsePoly, weightedSumPoly, zeroPoly]
  | cons pair pairs ih =>
    rcases pair with ⟨s, g⟩
    calc
      evalSparsePoly (weightedSumPoly (⟨s, g⟩ :: pairs)) x
          = evalSparsePoly (pairs.foldl (fun acc' (s, g) => addPoly acc' (mulPoly s g)) (mulPoly s g)) x := by
        simp [weightedSumPoly, addPoly, List.foldl, zeroPoly]
      _ = evalSparsePoly (addPoly (mulPoly s g) (weightedSumPoly pairs)) x := by
        rw [weightedSumPoly_foldl_add (mulPoly s g) pairs]
      _ = evalSparsePoly (mulPoly s g ++ weightedSumPoly pairs) x := by rw [addPoly]
      _ = evalSparsePoly (mulPoly s g) x + evalSparsePoly (weightedSumPoly pairs) x := by rw [evalSparsePoly_append]
      _ = (evalSparsePoly s x * evalSparsePoly g x) + (pairs.map (fun (s, g) => evalSparsePoly s x * evalSparsePoly g x)).sum := by
        simp [eval_mul, ih]
      _ = ((⟨s, g⟩ :: pairs).map (fun (s, g) => evalSparsePoly s x * evalSparsePoly g x)).sum := by simp

theorem sqPoly_eval_nonneg {n : ℕ} (q : SparsePoly n) (x : Fin n → ℝ) :
    evalSparsePoly (sqPoly q) x ≥ 0 := by
  rw [eval_sqPoly]
  exact sq_nonneg _

lemma evalTerm_add_same_exponent {n : ℕ} (t s : Term n) (x : Fin n → ℝ) (h_exp : t.expon = s.expon) :
    evalTerm t x + evalTerm s x = evalTerm { coeff := t.coeff + s.coeff, expon := t.expon } x := by
  have h_prod : ∏ i : Fin n, x i ^ t.expon i = ∏ i : Fin n, x i ^ s.expon i := by
    simpa [h_exp]
  simp [evalTerm, h_prod, add_mul, push_cast]

lemma eval_insertTerm {n : ℕ} (t : Term n) (acc : SparsePoly n) (x : Fin n → ℝ) :
    evalSparsePoly (insertTerm t acc) x = evalTerm t x + evalSparsePoly acc x := by
  induction acc generalizing t with
  | nil => simp [evalSparsePoly, insertTerm, evalTerm]
  | cons h rest ih =>
    by_cases h_eq : (t.expon == h.expon) = true
    · have hexp : t.expon = h.expon := by simpa using h_eq
      rw [insertTerm, if_pos h_eq, eval_cons]
      calc
        evalTerm { coeff := t.coeff + h.coeff, expon := h.expon } x + evalSparsePoly rest x
            = evalTerm { coeff := t.coeff + h.coeff, expon := t.expon } x + evalSparsePoly rest x := by
          rw [hexp]
        _ = (evalTerm t x + evalTerm h x) + evalSparsePoly rest x := by
          rw [evalTerm_add_same_exponent t h x hexp]
        _ = evalTerm t x + (evalTerm h x + evalSparsePoly rest x) := by ring
        _ = evalTerm t x + evalSparsePoly (h :: rest) x := by rw [eval_cons]
    · by_cases h_lt : exponentLt t.expon h.expon = true
      · -- exponentLt true: t goes before h
        have h_ne : t.expon ≠ h.expon := by
          intro heq
          apply h_eq
          simpa [heq]
        have h_eq_false' : (t.expon == h.expon) = false :=
          Bool.eq_false_of_not_eq_true (by
            intro h_eq_true
            apply h_ne
            simpa using h_eq_true)
        have h_ins : insertTerm t (h :: rest) = t :: h :: rest := by
          have h_eq_def : insertTerm t (h :: rest) =
            (if t.expon == h.expon then ⟨t.coeff + h.coeff, h.expon⟩ :: rest
             else if exponentLt t.expon h.expon then t :: h :: rest
             else h :: insertTerm t rest) := by rfl
          rw [h_eq_def, h_eq_false', h_lt]
          simp
        rw [h_ins, eval_cons]
      · -- exponentLt false: h stays before t, insert t into rest
        have h_ne : t.expon ≠ h.expon := by
          intro heq
          apply h_eq
          simpa [heq]
        have h_eq_false' : (t.expon == h.expon) = false :=
          Bool.eq_false_of_not_eq_true (by
            intro h_eq_true
            apply h_ne
            simpa using h_eq_true)
        have h_lt_false : exponentLt t.expon h.expon = false :=
          Bool.eq_false_of_not_eq_true h_lt
        have h_ins : insertTerm t (h :: rest) = h :: insertTerm t rest := by
          have h_eq_def : insertTerm t (h :: rest) =
            (if t.expon == h.expon then ⟨t.coeff + h.coeff, h.expon⟩ :: rest
             else if exponentLt t.expon h.expon then t :: h :: rest
             else h :: insertTerm t rest) := by rfl
          rw [h_eq_def, h_eq_false', h_lt_false]
          simp
        rw [h_ins]
        calc
          evalSparsePoly (h :: insertTerm t rest) x
              = evalTerm h x + evalSparsePoly (insertTerm t rest) x := by rw [eval_cons]
          _ = evalTerm h x + (evalTerm t x + evalSparsePoly rest x) := by rw [ih]
          _ = evalTerm t x + (evalTerm h x + evalSparsePoly rest x) := by ring
          _ = evalTerm t x + evalSparsePoly (h :: rest) x := by rw [eval_cons]

lemma eval_filter_nonzero_eq_eval {n : ℕ} (p : SparsePoly n) (x : Fin n → ℝ) :
    evalSparsePoly (p.filter fun t : Term n => t.coeff != 0) x = evalSparsePoly p x := by
  induction p with
  | nil => rfl
  | cons t ts ih =>
    by_cases h : t.coeff = 0
    · calc
        evalSparsePoly (List.filter (fun t : Term n => t.coeff != 0) (t :: ts)) x
            = evalSparsePoly (List.filter (fun t : Term n => t.coeff != 0) ts) x := by
          simp [h, List.filter]
        _ = evalSparsePoly ts x := by rw [ih]
        _ = evalTerm t x + evalSparsePoly ts x := by
          simp [evalTerm, h]
        _ = evalSparsePoly (t :: ts) x := by rw [eval_cons]
    · have h' : t.coeff ≠ 0 := by
        intro hzero
        exact h hzero
      have h_bool : (t.coeff != 0) = true := by
        simp [h']
      have hfilter : List.filter (fun t : Term n => t.coeff != 0) (t :: ts) = t :: List.filter (fun t : Term n => t.coeff != 0) ts := by
        simp [List.filter, h_bool]
      calc
        evalSparsePoly (List.filter (fun t : Term n => t.coeff != 0) (t :: ts)) x
            = evalSparsePoly (t :: List.filter (fun t : Term n => t.coeff != 0) ts) x := by rw [hfilter]
        _ = evalTerm t x + evalSparsePoly (List.filter (fun t : Term n => t.coeff != 0) ts) x := by rw [eval_cons]
        _ = evalTerm t x + evalSparsePoly ts x := by rw [ih]
        _ = evalSparsePoly (t :: ts) x := by rw [eval_cons]

lemma foldl_insertTerm_eval {n : ℕ} (acc : SparsePoly n) (p : SparsePoly n) (x : Fin n → ℝ) :
    evalSparsePoly (p.foldl (fun acc' t => insertTerm t acc') acc) x = evalSparsePoly acc x + evalSparsePoly p x := by
  induction p generalizing acc with
  | nil => simp [evalSparsePoly]
  | cons t ts ih =>
    calc
      evalSparsePoly ((t :: ts).foldl (fun acc' t => insertTerm t acc') acc) x
          = evalSparsePoly (ts.foldl (fun acc' t => insertTerm t acc') (insertTerm t acc)) x := rfl
      _ = evalSparsePoly (insertTerm t acc) x + evalSparsePoly ts x := by rw [ih (insertTerm t acc)]
      _ = (evalTerm t x + evalSparsePoly acc x) + evalSparsePoly ts x := by rw [eval_insertTerm]
      _ = evalSparsePoly acc x + (evalTerm t x + evalSparsePoly ts x) := by ring
      _ = evalSparsePoly acc x + evalSparsePoly (t :: ts) x := by rw [eval_cons]

lemma eval_normalizePoly_eq_eval {n : ℕ} (p : SparsePoly n) (x : Fin n → ℝ) :
    evalSparsePoly (normalizePoly p) x = evalSparsePoly p x := by
  unfold normalizePoly
  have hfoldl : evalSparsePoly (p.foldl (fun acc' t => insertTerm t acc') []) x = evalSparsePoly p x := by
    simpa [evalSparsePoly] using foldl_insertTerm_eval [] p x
  have hfilter : evalSparsePoly ((p.foldl (fun acc' t => insertTerm t acc') []).filter
      fun t : Term n => t.coeff != 0) x = evalSparsePoly (p.foldl (fun acc' t => insertTerm t acc') []) x :=
    eval_filter_nonzero_eq_eval _ x
  rw [hfilter, hfoldl]

lemma and_eq_true_iff (a b : Bool) : (a && b) = true ↔ a = true ∧ b = true := by
  constructor
  · intro h
    have ha : a = true := by
      cases a
      · simp at h
      · rfl
    have hb : b = true := by
      rw [ha] at h
      simp at h
      exact h
    exact ⟨ha, hb⟩
  · intro ⟨ha, hb⟩; simp [ha, hb]

lemma polyEqNormalized_eq {n : ℕ} (p q : List (Term n)) (h : polyEqNormalized p q = true) : p = q := by
  induction p generalizing q with
  | nil =>
    cases q
    · rfl
    · simp [polyEqNormalized] at h
  | cons hp rp ih =>
    cases q
    · simp [polyEqNormalized] at h
    · rename_i hq rq
      simp [polyEqNormalized] at h
      rcases h with ⟨⟨hexp, hcoeff⟩, hrest⟩
      have hrp_eq_rq : rp = rq := ih rq hrest
      have hhp_eq_hq : hp = hq := by
        cases hp; cases hq
        dsimp at hcoeff hexp
        subst hcoeff; subst hexp; rfl
      calc
        hp :: rp = hq :: rp := by rw [hhp_eq_hq]
        _ = hq :: rq := by rw [hrp_eq_rq]

lemma polyEqNormalized_eval_eq {n : ℕ} (p q : List (Term n)) (h : polyEqNormalized p q = true) (x : Fin n → ℝ) :
    evalSparsePoly p x = evalSparsePoly q x := by
  have h_eq : p = q := polyEqNormalized_eq p q h
  subst h_eq; rfl

lemma polyEq_eval_eq {n : ℕ} (p q : SparsePoly n) (h : polyEq p q = true) (x : Fin n → ℝ) :
    evalSparsePoly p x = evalSparsePoly q x := by
  have h_norm_eq : polyEqNormalized (normalizePoly p) (normalizePoly q) = true := h
  have h_eval_norm_eq : evalSparsePoly (normalizePoly p) x = evalSparsePoly (normalizePoly q) x :=
    polyEqNormalized_eval_eq _ _ h_norm_eq x
  have h_eval_p_norm : evalSparsePoly (normalizePoly p) x = evalSparsePoly p x := eval_normalizePoly_eq_eval p x
  have h_eval_q_norm : evalSparsePoly (normalizePoly q) x = evalSparsePoly q x := eval_normalizePoly_eq_eval q x
  rw [← h_eval_p_norm, h_eval_norm_eq, h_eval_q_norm]

theorem verifyCertificate_sound {n : ℕ} (cert : SDPCertificate n)
    (x : Fin n → ℝ)
    (h_verify : verifyCertificate cert = true)
    (h_weight_sos : ∀ pair ∈ cert.weighted_pairs,
      evalSparsePoly pair.1 x ≥ 0)
    (h_constraints : ∀ pair ∈ cert.weighted_pairs,
      evalSparsePoly pair.2 x ≥ 0) :
    evalSparsePoly cert.target x ≥ 0 := by
  have hpoly_eq : polyEq (addPoly (sumSqPoly cert.sos_components) (weightedSumPoly cert.weighted_pairs)) cert.target = true := h_verify
  have heval_eq : evalSparsePoly (addPoly (sumSqPoly cert.sos_components) (weightedSumPoly cert.weighted_pairs)) x = evalSparsePoly cert.target x :=
    polyEq_eval_eq _ _ hpoly_eq x
  rw [← heval_eq]
  have hsos_nonneg : evalSparsePoly (sumSqPoly cert.sos_components) x ≥ 0 := by
    rw [eval_sumSqPoly]
    refine List.sum_nonneg ?_
    intro y hy
    rcases List.mem_map.mp hy with ⟨q, hq, rfl⟩
    exact pow_two_nonneg _
  have hweighted_nonneg : evalSparsePoly (weightedSumPoly cert.weighted_pairs) x ≥ 0 := by
    rw [eval_weightedSumPoly]
    refine List.sum_nonneg ?_
    intro y hy
    rcases List.mem_map.mp hy with ⟨⟨s, g⟩, hpair, rfl⟩
    have hs_nonneg : evalSparsePoly s x ≥ 0 := h_weight_sos ⟨s, g⟩ hpair
    have hg_nonneg : evalSparsePoly g x ≥ 0 := h_constraints ⟨s, g⟩ hpair
    nlinarith
  have h_add : evalSparsePoly (addPoly (sumSqPoly cert.sos_components) (weightedSumPoly cert.weighted_pairs)) x =
    evalSparsePoly (sumSqPoly cert.sos_components) x + evalSparsePoly (weightedSumPoly cert.weighted_pairs) x := by
    simp [evalSparsePoly_append, addPoly]
  rw [h_add]
  nlinarith

end Soundness

-- ============================================================
-- §7  CONVENIENCE CONSTRUCTORS (for certificate data files)
-- ============================================================

section Constructors

/-- Build a SparsePoly from a list of (coefficient, exponent-list) pairs.
    The exponent list is padded/truncated to length n.
    Example: mkPoly 4 [(3, [2,0,0,0]), (1, [0,0,1,0])] = 3x₀² + x₂ -/
def mkPoly (n : ℕ) (terms : List (ℚ × List ℕ)) : SparsePoly n :=
  terms.map fun (c, es) =>
    { coeff := c
      expon := fun i =>
        if h : i.val < es.length then es[i.val]'h else 0 }

/-- Build a certificate from raw data (as output by the Python shim). -/
def mkCertificate (n : ℕ)
    (sos_data : List (List (ℚ × List ℕ)))
    (weighted_data : List (List (ℚ × List ℕ) × List (ℚ × List ℕ)))
    (target_data : List (ℚ × List ℕ))
    (degree level : ℕ) : SDPCertificate n :=
  { sos_components := sos_data.map (mkPoly n)
    weighted_pairs := weighted_data.map fun (s, g) => (mkPoly n s, mkPoly n g)
    target := mkPoly n target_data
    degree := degree
    level := level }

end Constructors

-- ============================================================
-- §8  SIMPLE TEST CASES
-- ============================================================

section Tests

/-- Test: x₀² + x₁² ≥ 0.
    SOS certificate: q₀ = x₀, q₁ = x₁.
    Σ qᵢ² = x₀² + x₁² = p. Trivial. -/
def testCertSimple : SDPCertificate 2 :=
  mkCertificate 2
    -- SOS components: [x₀, x₁]
    [ [(1, [1, 0])],   -- q₀ = x₀
      [(1, [0, 1])] ]  -- q₁ = x₁
    -- No weighted pairs
    []
    -- Target: x₀² + x₁²
    [(1, [2, 0]), (1, [0, 2])]
    -- degree = 2, level = 0
    2 0

-- Verify the simple test case.
#eval! verifyCertificate testCertSimple  -- Expected: true

/-- Test: (x₀ + x₁)² = x₀² + 2x₀x₁ + x₁².
    SOS certificate: q₀ = x₀ + x₁.
    q₀² = x₀² + 2x₀x₁ + x₁² = p. -/
def testCertBinomial : SDPCertificate 2 :=
  mkCertificate 2
    -- SOS components: [x₀ + x₁]
    [ [(1, [1, 0]), (1, [0, 1])] ]  -- q₀ = x₀ + x₁
    -- No weighted pairs
    []
    -- Target: x₀² + 2x₀x₁ + x₁²
    [(1, [2, 0]), (2, [1, 1]), (1, [0, 2])]
    -- degree = 2, level = 0
    2 0

#eval! verifyCertificate testCertBinomial  -- Expected: true

/-- Test: x₀² + x₁² + 1 ≥ 0.
    SOS certificate: q₀ = x₀, q₁ = x₁, q₂ = 1.
    Σ qᵢ² = x₀² + x₁² + 1 = p. -/
def testCertWithConstant : SDPCertificate 2 :=
  mkCertificate 2
    [ [(1, [1, 0])],   -- q₀ = x₀
      [(1, [0, 1])],   -- q₁ = x₁
      [(1, [0, 0])] ]  -- q₂ = 1
    []
    [(1, [2, 0]), (1, [0, 2]), (1, [0, 0])]
    2 0

#eval! verifyCertificate testCertWithConstant  -- Expected: true

/-- Test with weighted constraint: p = x₀² + x₀ ≥ 0 on K = {x₀ ≥ 0}.
    Certificate: p = x₀² + x₀ · 1  (where g₀ = x₀, s₀ = 1)
    So: sos_components = [], weighted = [(1, x₀)]
    But x₀² needs an SOS component too.
    Actually: p = x₀² + x₀ = x₀² + 1·g₀ where g₀ = x₀.
    SOS components: [x₀] (for x₀²), weighted: [([1], [x₀])] (for x₀). -/
def testCertWeighted : SDPCertificate 1 :=
  mkCertificate 1
    [ [(1, [1])] ]          -- q₀ = x₀ → q₀² = x₀²
    [ ([(1, [0])],          -- s₀ = 1 (constant, SOS-compatible)
       [(1, [1])]) ]        -- g₀ = x₀ (constraint)
    [(1, [2]), (1, [1])]    -- p = x₀² + x₀
    2 1

#eval! verifyCertificate testCertWeighted  -- Expected: true

/-- Negative test: wrong certificate should return false. -/
def testCertWrong : SDPCertificate 2 :=
  mkCertificate 2
    [ [(1, [1, 0])] ]  -- q₀ = x₀ → q₀² = x₀² (missing x₁²)
    []
    [(1, [2, 0]), (1, [0, 2])]  -- p = x₀² + x₁²
    2 0

#eval! verifyCertificate testCertWrong  -- Expected: false

end Tests

end Semantics.SDPVerify
