import Mathlib
import Definitions.Def_WeierstrassCurve_ReductionMap

set_option autoImplicit false

open IsLocalRing Polynomial

namespace ValuationSubring

section ResidueAlgClosed

variable {L : Type*} [Field L] (A : ValuationSubring L)

instance instIsAlgClosedResidueField [IsAlgClosed L] :
    IsAlgClosed (ResidueField A) := by
  refine IsAlgClosed.of_exists_root _ (fun f hf_monic hf_irr => ?_)

  have hf_lifts : f ∈ lifts (residue A) := by
    rw [lifts_iff_coeff_lifts]
    exact fun n => residue_surjective (f.coeff n)
  obtain ⟨g, hg_map, -, hg_monic⟩ := lifts_and_degree_eq_and_monic hf_lifts hf_monic

  have hgL_monic : (g.map (algebraMap A L)).Monic := hg_monic.map _
  have hgL_deg : (g.map (algebraMap A L)).degree ≠ 0 := by
    rw [hg_monic.degree_map]
    intro h0

    have hg_nat : g.natDegree = 0 := natDegree_eq_zero_iff_degree_le_zero.mpr (le_of_eq h0)
    have hf_deg : f.natDegree = 0 := by
      rw [← hg_map, hg_monic.natDegree_map]
      exact hg_nat
    have hf1 : f = 1 := (hf_monic.natDegree_eq_zero).mp hf_deg
    exact hf_irr.not_isUnit (by rw [hf1]; exact isUnit_one)
  obtain ⟨α, hα⟩ := IsAlgClosed.exists_root _ hgL_deg

  have hα_int : IsIntegral A α := by
    refine ⟨g, hg_monic, ?_⟩
    rwa [IsRoot.def, eval_map] at hα
  obtain ⟨a, ha⟩ := IsIntegrallyClosed.isIntegral_iff.mp hα_int

  refine ⟨residue A a, ?_⟩
  have hga : g.eval a = 0 := by
    have : algebraMap A L (g.eval a) = 0 := by
      have hcomm : algebraMap A L (g.eval a) = (g.map (algebraMap A L)).eval α := by
        rw [← ha, eval_map, eval₂_hom]
      rw [hcomm]
      exact hα
    exact (map_eq_zero_iff _ Subtype.coe_injective).mp this
  have : f.eval (residue A a) = residue A (g.eval a) := by
    rw [← hg_map, eval_map, eval₂_hom]
  rw [this, hga, map_zero]

variable {A}

theorem pow_residueChar_bijective [IsAlgClosed L] {q : ℕ} (hq : q.Prime)
    (hA : A.LiesOverPrime q) :
    Function.Bijective (fun x : ResidueField A => x ^ q) := by
  haveI : CharP (ResidueField A) q := charP_residueField_of_liesOverPrime_def hq hA
  haveI : Fact q.Prime := ⟨hq⟩
  constructor
  ·
    intro x y hxy
    have hxy' : x ^ q = y ^ q := hxy
    have hsub : (x - y) ^ q = 0 := by
      rw [sub_pow_char, hxy', sub_self]
    have := pow_eq_zero_iff hq.ne_zero |>.mp hsub
    exact sub_eq_zero.mp this
  ·
    intro x
    obtain ⟨y, hy⟩ := IsAlgClosed.exists_pow_nat_eq x hq.pos
    exact ⟨y, hy⟩

end ResidueAlgClosed

variable {L : Type*} [Field L]

section IntegerUnits

variable {A : ValuationSubring L} {ℓ : ℕ}

theorem intCast_isUnit_of_not_dvd (hℓ : ℓ.Prime) (hA : A.LiesOverPrime ℓ) {n : ℤ}
    (hn : ¬(ℓ : ℤ) ∣ n) : IsUnit ((n : ℤ) : A) := by
  by_contra hunit

  have hnmem : ((n : ℤ) : A) ∈ maximalIdeal A := by
    rw [IsLocalRing.mem_maximalIdeal]
    exact hunit

  have hℓmem : ((ℓ : ℕ) : A) ∈ maximalIdeal A := natCast_mem_maximalIdeal_of_liesOverPrime hA

  obtain ⟨u, v, huv⟩ := (Nat.prime_iff_prime_int.mp hℓ).coprime_iff_not_dvd.mpr hn

  have h1 : (1 : A) ∈ maximalIdeal A := by
    have h := congrArg (fun z : ℤ => ((z : ℤ) : A)) huv
    simp only [Int.cast_add, Int.cast_mul, Int.cast_one, Int.cast_natCast] at h
    rw [← h]
    exact Ideal.add_mem _ (Ideal.mul_mem_left _ _ hℓmem) (Ideal.mul_mem_left _ _ hnmem)
  exact (maximalIdeal.isMaximal A).ne_top ((Ideal.eq_top_iff_one _).mpr h1)

theorem residue_intCast_ne_zero (hℓ : ℓ.Prime) (hA : A.LiesOverPrime ℓ) {n : ℤ}
    (hn : ¬(ℓ : ℤ) ∣ n) : residue A ((n : ℤ) : A) ≠ 0 :=
  (residue_ne_zero_iff_isUnit _).mpr (intCast_isUnit_of_not_dvd hℓ hA hn)

theorem gate_not_liesOverPrime_of_ne (hℓ : ℓ.Prime) (hA : A.LiesOverPrime ℓ) {q : ℕ}
    (hq : q.Prime) (hne : q ≠ ℓ) : ¬A.LiesOverPrime q := by
  intro hq'
  have hqmem : ((q : ℕ) : A) ∈ maximalIdeal A := natCast_mem_maximalIdeal_of_liesOverPrime hq'
  have hqdvd : ¬(ℓ : ℤ) ∣ (q : ℤ) := by
    intro h
    rcases (Nat.prime_dvd_prime_iff_eq hℓ hq).mp (Int.natCast_dvd_natCast.mp h) with rfl
    exact hne rfl
  have := intCast_isUnit_of_not_dvd hℓ hA hqdvd
  rw [IsLocalRing.mem_maximalIdeal] at hqmem
  exact hqmem (by exact_mod_cast this)

end IntegerUnits

section Algebraicity

variable (A : ValuationSubring L) {ℓ : ℕ}

theorem isAlgebraic_residue [CharZero L] [Algebra ℚ L]
    [Algebra.IsAlgebraic ℚ L] (hℓ : ℓ.Prime)
    [Algebra (ZMod ℓ) (ResidueField A)] (a : A) :
    IsAlgebraic (ZMod ℓ) (residue A a) := by

  obtain ⟨p, hp0, hpa⟩ := Algebra.IsAlgebraic.isAlgebraic (R := ℚ) (a : L)

  set P₀ : ℤ[X] := IsLocalization.integerNormalization (nonZeroDivisors ℤ) p with hP₀def
  have hP₀a : aeval (a : L) P₀ = 0 :=
    IsLocalization.integerNormalization_aeval_eq_zero (nonZeroDivisors ℤ) p hpa
  have hP₀0 : P₀ ≠ 0 := fun h =>
    hp0 ((IsLocalization.integerNormalization_eq_zero_iff le_rfl p).mp h)

  set P : ℤ[X] := P₀.primPart with hPdef
  have hPa : aeval (a : L) P = 0 := aeval_primPart_eq_zero hP₀0 hP₀a
  have hPprim : P.IsPrimitive := P₀.isPrimitive_primPart
  have hP0 : P ≠ 0 := P₀.primPart_ne_zero

  have hcoeff : ∃ i, ¬(ℓ : ℤ) ∣ P.coeff i := by
    by_contra h
    simp only [not_exists, not_not] at h
    have hu : IsUnit ((ℓ : ℤ)) := hPprim _ ((C_dvd_iff_dvd_coeff _ _).mpr h)
    rw [Int.isUnit_iff] at hu
    have h2 := hℓ.two_le
    omega

  have hPaA : aeval a P = 0 := by
    have hmap : algebraMap A L (aeval a P) = 0 := by
      rw [← Polynomial.aeval_algebraMap_apply]
      exact hPa
    exact (map_eq_zero_iff _ Subtype.coe_injective).mp hmap

  have hPres : aeval (residue A a) P = 0 := by
    have hcomm : aeval (residue A a) P = (residue A) (aeval a P) :=
      Polynomial.aeval_algHom_apply (RingHom.toIntAlgHom (residue A)) a P
    rw [hcomm, hPaA, map_zero]

  refine ⟨P.map (algebraMap ℤ (ZMod ℓ)), ?_, ?_⟩
  ·
    obtain ⟨i, hi⟩ := hcoeff
    intro h0
    apply hi
    have hzero : (P.map (algebraMap ℤ (ZMod ℓ))).coeff i = 0 := by rw [h0]; simp
    rw [coeff_map] at hzero
    exact (ZMod.intCast_zmod_eq_zero_iff_dvd _ _).mp hzero
  ·
    rw [aeval_def, eval₂_map,
      show (algebraMap (ZMod ℓ) (ResidueField A)).comp (algebraMap ℤ (ZMod ℓ))
          = algebraMap ℤ (ResidueField A) from RingHom.ext_int _ _,
      ← aeval_def]
    exact hPres

theorem gate_isAlgebraic_int_vacuous_of_charP {R : Type*} [CommRing R] [Nontrivial R]
    {ℓ : ℕ} (hℓ : ℓ.Prime) [CharP R ℓ] (x : R) : IsAlgebraic ℤ x := by
  refine ⟨C (ℓ : ℤ) * X, ?_, ?_⟩
  · intro h
    have hc := congrArg (fun q : ℤ[X] => q.coeff 1) h
    simp only [coeff_C_mul, coeff_X_one, mul_one, coeff_zero] at hc
    have h2 := hℓ.two_le
    omega
  · simp

end Algebraicity

section Identification

variable (A : ValuationSubring L) {ℓ : ℕ}

theorem isAlgClosure_residueField [IsAlgClosed L] [CharZero L] [Algebra ℚ L]
    [Algebra.IsAlgebraic ℚ L] [Fact ℓ.Prime]
    [Algebra (ZMod ℓ) (ResidueField A)] :
    IsAlgClosure (ZMod ℓ) (ResidueField A) := by
  refine ⟨inferInstance, ⟨fun x => ?_⟩⟩
  obtain ⟨a, rfl⟩ := residue_surjective (R := A) x
  exact isAlgebraic_residue A Fact.out a

noncomputable def residueFieldEquivAlgebraicClosure [IsAlgClosed L] [CharZero L] [Algebra ℚ L]
    [Algebra.IsAlgebraic ℚ L] [Fact ℓ.Prime] (hA : A.LiesOverPrime ℓ) :
    ResidueField A ≃+* AlgebraicClosure (ZMod ℓ) := by
  haveI hchar : CharP (ResidueField A) ℓ := charP_residueField_of_liesOverPrime_def Fact.out hA
  letI halg : Algebra (ZMod ℓ) (ResidueField A) := ZMod.algebra (ResidueField A) ℓ

  haveI htf : Module.IsTorsionFree (ZMod ℓ) (ResidueField A) := by
    refine Module.IsTorsionFree.of_smul_eq_zero (fun r m h => ?_)
    rw [Algebra.smul_def] at h
    rcases mul_eq_zero.mp h with h' | h'
    · exact Or.inl ((map_eq_zero_iff _ (algebraMap (ZMod ℓ) (ResidueField A)).injective).mp h')
    · exact Or.inr h'
  haveI hcl : IsAlgClosure (ZMod ℓ) (ResidueField A) := isAlgClosure_residueField A
  exact (IsAlgClosure.equiv (ZMod ℓ) (ResidueField A) (AlgebraicClosure (ZMod ℓ))).toRingEquiv

end Identification

section ReduceAt

variable {ℓ : ℕ} [Fact ℓ.Prime]
variable (A : ValuationSubring L) [IsAlgClosed L] [CharZero L] [Algebra ℚ L]
  [Algebra.IsAlgebraic ℚ L]
variable (hA : A.LiesOverPrime ℓ)

open Classical in

noncomputable def reduceAt (a : L) : AlgebraicClosure (ZMod ℓ) :=
  if h : a ∈ A then residueFieldEquivAlgebraicClosure A hA (residue A ⟨a, h⟩) else 0

variable {A hA}

theorem reduceAt_of_mem {a : L} (h : a ∈ A) :
    reduceAt A hA a = residueFieldEquivAlgebraicClosure A hA (residue A ⟨a, h⟩) :=
  dif_pos h

theorem reduceAt_of_notMem {a : L} (h : a ∉ A) : reduceAt A hA a = 0 :=
  dif_neg h

@[simp]
theorem reduceAt_coe (a : A) :
    reduceAt A hA (a : L) = residueFieldEquivAlgebraicClosure A hA (residue A a) :=
  reduceAt_of_mem a.2

@[simp]
theorem reduceAt_zero : reduceAt A hA (0 : L) = 0 := by
  have h := reduceAt_coe (A := A) (hA := hA) (0 : A)
  simpa using h

@[simp]
theorem reduceAt_one : reduceAt A hA (1 : L) = 1 := by
  have h := reduceAt_coe (A := A) (hA := hA) (1 : A)
  simpa using h

theorem reduceAt_add {a b : L} (ha : a ∈ A) (hb : b ∈ A) :
    reduceAt A hA (a + b) = reduceAt A hA a + reduceAt A hA b := by
  rw [reduceAt_of_mem ha, reduceAt_of_mem hb, reduceAt_of_mem (A.add_mem _ _ ha hb),
    show (⟨a + b, A.add_mem _ _ ha hb⟩ : A) = ⟨a, ha⟩ + ⟨b, hb⟩ from rfl, map_add, map_add]

theorem reduceAt_mul {a b : L} (ha : a ∈ A) (hb : b ∈ A) :
    reduceAt A hA (a * b) = reduceAt A hA a * reduceAt A hA b := by
  rw [reduceAt_of_mem ha, reduceAt_of_mem hb, reduceAt_of_mem (A.mul_mem _ _ ha hb),
    show (⟨a * b, A.mul_mem _ _ ha hb⟩ : A) = ⟨a, ha⟩ * ⟨b, hb⟩ from rfl, map_mul, map_mul]

theorem reduceAt_surjective : Function.Surjective (reduceAt A hA) := by
  intro y
  obtain ⟨a, ha⟩ :=
    residue_surjective (R := A) ((residueFieldEquivAlgebraicClosure A hA).symm y)
  exact ⟨(a : L), by rw [reduceAt_coe, ha, RingEquiv.apply_symm_apply]⟩

theorem reduceAt_eq_zero_iff_of_mem {a : L} (h : a ∈ A) :
    reduceAt A hA a = 0 ↔ (⟨a, h⟩ : A) ∈ maximalIdeal A := by
  rw [reduceAt_of_mem h,
    map_eq_zero_iff _ (residueFieldEquivAlgebraicClosure A hA).injective]
  exact residue_eq_zero_iff _

@[simp]
theorem reduceAt_intCast (n : ℤ) :
    reduceAt A hA ((n : ℤ) : L) = ((n : ℤ) : AlgebraicClosure (ZMod ℓ)) := by
  have hmem : ((n : ℤ) : L) = (((n : ℤ) : A) : L) := by norm_cast
  rw [hmem, reduceAt_coe, map_intCast (residue A) n]
  exact map_intCast (residueFieldEquivAlgebraicClosure A hA) n

@[simp]
theorem reduceAt_natCast (n : ℕ) :
    reduceAt A hA ((n : ℕ) : L) = ((n : ℕ) : AlgebraicClosure (ZMod ℓ)) := by
  have h := reduceAt_intCast (A := A) (hA := hA) (n : ℤ)
  push_cast at h
  exact h

theorem reduceAt_residueChar : reduceAt A hA ((ℓ : ℕ) : L) = 0 := by
  rw [reduceAt_natCast]
  exact CharP.cast_eq_zero (AlgebraicClosure (ZMod ℓ)) ℓ

theorem reduceAt_intCast_ne_zero {n : ℤ} (hn : ¬(ℓ : ℤ) ∣ n) :
    reduceAt A hA ((n : ℤ) : L) ≠ 0 := by
  rw [reduceAt_intCast]
  intro h
  apply hn
  rw [show ((n : ℤ) : AlgebraicClosure (ZMod ℓ))
      = algebraMap (ZMod ℓ) (AlgebraicClosure (ZMod ℓ)) ((n : ℤ) : ZMod ℓ) from
    (map_intCast _ n).symm] at h
  exact (ZMod.intCast_zmod_eq_zero_iff_dvd n ℓ).mp
    ((map_eq_zero_iff _ (algebraMap (ZMod ℓ) (AlgebraicClosure (ZMod ℓ))).injective).mp h)

end ReduceAt

end ValuationSubring
