-- Mathlib/NumberTheory/LegendreSymbol/GaussEisensteinLemmas.lean
theorem sum_mul_div_add_sum_mul_div_eq_mul (p q : ℕ) [hp : Fact p.Prime] (hq0 : (q : ZMod p) ≠ 0) :
    ∑ a ∈ Ico 1 (p / 2).succ, a * q / p + ∑ a ∈ Ico 1 (q / 2).succ, a * p / q =
    p / 2 * (q / 2)

Proof state:
p q : ℕ
hp : Fact (Nat.Prime p)
hq0 : ↑q ≠ 0
hswap :
  #(filter (fun x => x.2 * q ≤ x.1 * p) (Ico 1 (q / 2).succ ×ˢ Ico 1 (p / 2).succ)) =
    #(filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
⊢ ∑ a ∈ Ico 1 (p / 2).succ, a * q / p + ∑ a ∈ Ico 1 (q / 2).succ, a * p / q = p / 2 * (q / 2)

Tactic:
have hdisj :
  Disjoint {x ∈ Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ | x.2 * p ≤ x.1 * q}
    {x ∈ Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ | x.1 * q ≤ x.2 * p} := by
  apply disjoint_filter.2 fun x hx hpq hqp => ?_
  have hxp : x.1 < p := lt_of_le_of_lt
    (show x.1 ≤ p / 2 by simp_all only [Nat.lt_succ_iff, mem_Ico, mem_product])
    (Nat.div_lt_self hp.1.pos (by decide))
  have : (x.1 : ZMod p) = 0 := by
    simpa [hq0] using congr_arg ((↑) : ℕ → ZMod p) (le_antisymm hpq hqp)
  apply_fun ZMod.val at this
  rw [val_cast_of_lt hxp, val_zero] at this
  simp only [this, nonpos_iff_eq_zero, mem_Ico, one_ne_zero, false_and, mem_product] at hx

Result:
p q : ℕ
hp : Fact (Nat.Prime p)
hq0 : ↑q ≠ 0
hswap :
  #(filter (fun x => x.2 * q ≤ x.1 * p) (Ico 1 (q / 2).succ ×ˢ Ico 1 (p / 2).succ)) =
    #(filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
hdisj :
  Disjoint (filter (fun x => x.2 * p ≤ x.1 * q) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
    (filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
⊢ ∑ a ∈ Ico 1 (p / 2).succ, a * q / p + ∑ a ∈ Ico 1 (q / 2).succ, a * p / q = p / 2 * (q / 2)

Proof state:
p q : ℕ
hp : Fact (Nat.Prime p)
hq0 : ↑q ≠ 0
hswap :
  #(filter (fun x => x.2 * q ≤ x.1 * p) (Ico 1 (q / 2).succ ×ˢ Ico 1 (p / 2).succ)) =
    #(filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
hdisj :
  Disjoint (filter (fun x => x.2 * p ≤ x.1 * q) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
    (filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
x : ℕ × ℕ
this : x.2 * p ≤ x.1 * q ∨ x.1 * q ≤ x.2 * p
⊢ ((1 ≤ x.1 ∧ x.1 < (p / 2).succ) ∧ 1 ≤ x.2 ∧ x.2 < (q / 2).succ) ∧ x.2 * p ≤ x.1 * q ∨
      ((1 ≤ x.1 ∧ x.1 < (p / 2).succ) ∧ 1 ≤ x.2 ∧ x.2 < (q / 2).succ) ∧ x.1 * q ≤ x.2 * p ↔
    (1 ≤ x.1 ∧ x.1 < (p / 2).succ) ∧ 1 ≤ x.2 ∧ x.2 < (q / 2).succ

Tactic:
tauto

Result:
no goals

Proof state:
p q : ℕ
hp : Fact (Nat.Prime p)
hq0 : ↑q ≠ 0
hswap :
  #(filter (fun x => x.2 * q ≤ x.1 * p) (Ico 1 (q / 2).succ ×ˢ Ico 1 (p / 2).succ)) =
    #(filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
hdisj :
  Disjoint (filter (fun x => x.2 * p ≤ x.1 * q) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
    (filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
x : ℕ × ℕ
⊢ x ∈
      filter (fun x => x.2 * p ≤ x.1 * q) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ) ∪
        filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ) ↔
    x ∈ Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ

Tactic:
have := le_total (x.2 * p) (x.1 * q)

Result:
p q : ℕ
hp : Fact (Nat.Prime p)
hq0 : ↑q ≠ 0
hswap :
  #(filter (fun x => x.2 * q ≤ x.1 * p) (Ico 1 (q / 2).succ ×ˢ Ico 1 (p / 2).succ)) =
    #(filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
hdisj :
  Disjoint (filter (fun x => x.2 * p ≤ x.1 * q) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
    (filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
x : ℕ × ℕ
this : x.2 * p ≤ x.1 * q ∨ x.1 * q ≤ x.2 * p
⊢ x ∈
      filter (fun x => x.2 * p ≤ x.1 * q) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ) ∪
        filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ) ↔
    x ∈ Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ

Proof state:
p q : ℕ
hp : Fact (Nat.Prime p)
hq0 : ↑q ≠ 0
x✝ : ℕ × ℕ
fst✝ snd✝ : ℕ
⊢ (fst✝, snd✝) ∈ filter (fun x => x.2 * q ≤ x.1 * p) (Ico 1 (q / 2).succ ×ˢ Ico 1 (p / 2).succ) ↔
    (Equiv.prodComm ℕ ℕ) (fst✝, snd✝) ∈ filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ)

Tactic:
simp +contextual only [mem_filter, and_self_iff, Prod.swap_prod_mk,
  forall_true_iff, mem_product, Equiv.prodComm_apply, and_assoc, and_left_comm]

Result:
no goals

Proof state:
p q : ℕ
hp : Fact (Nat.Prime p)
hq0 : ↑q ≠ 0
hswap :
  #(filter (fun x => x.2 * q ≤ x.1 * p) (Ico 1 (q / 2).succ ×ˢ Ico 1 (p / 2).succ)) =
    #(filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
x : ℕ × ℕ
hx : x ∈ Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ
hpq : x.2 * p ≤ x.1 * q
hqp : x.1 * q ≤ x.2 * p
⊢ 1 < 2

Tactic:
decide

Result:
no goals

Proof state:
p q : ℕ
hp : Fact (Nat.Prime p)
hq0 : ↑q ≠ 0
hswap :
  #(filter (fun x => x.2 * q ≤ x.1 * p) (Ico 1 (q / 2).succ ×ˢ Ico 1 (p / 2).succ)) =
    #(filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
hdisj :
  Disjoint (filter (fun x => x.2 * p ≤ x.1 * q) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
    (filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
hunion :
  filter (fun x => x.2 * p ≤ x.1 * q) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ) ∪
      filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ) =
    Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ
⊢ #(Ico 1 (p / 2).succ) * #(Ico 1 (q / 2).succ) = p / 2 * (q / 2)

Tactic:
simp only [card_Ico, tsub_zero, succ_sub_succ_eq_sub]

Result:
no goals

Proof state:
p q : ℕ
hp : Fact (Nat.Prime p)
hq0 : ↑q ≠ 0
hswap :
  #(filter (fun x => x.2 * q ≤ x.1 * p) (Ico 1 (q / 2).succ ×ˢ Ico 1 (p / 2).succ)) =
    #(filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
⊢ Disjoint (filter (fun x => x.2 * p ≤ x.1 * q) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
    (filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))

Tactic:
apply disjoint_filter.2 fun x hx hpq hqp => ?_

Result:
p q : ℕ
hp : Fact (Nat.Prime p)
hq0 : ↑q ≠ 0
hswap :
  #(filter (fun x => x.2 * q ≤ x.1 * p) (Ico 1 (q / 2).succ ×ˢ Ico 1 (p / 2).succ)) =
    #(filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
x : ℕ × ℕ
hx : x ∈ Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ
hpq : x.2 * p ≤ x.1 * q
hqp : x.1 * q ≤ x.2 * p
⊢ False

Proof state:
p q : ℕ
hp : Fact (Nat.Prime p)
hq0 : ↑q ≠ 0
hswap :
  #(filter (fun x => x.2 * q ≤ x.1 * p) (Ico 1 (q / 2).succ ×ˢ Ico 1 (p / 2).succ)) =
    #(filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
x : ℕ × ℕ
hx : x ∈ Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ
hpq : x.2 * p ≤ x.1 * q
hqp : x.1 * q ≤ x.2 * p
⊢ False

Tactic:
have hxp : x.1 < p := lt_of_le_of_lt
  (show x.1 ≤ p / 2 by simp_all only [Nat.lt_succ_iff, mem_Ico, mem_product])
  (Nat.div_lt_self hp.1.pos (by decide))

Result:
p q : ℕ
hp : Fact (Nat.Prime p)
hq0 : ↑q ≠ 0
hswap :
  #(filter (fun x => x.2 * q ≤ x.1 * p) (Ico 1 (q / 2).succ ×ˢ Ico 1 (p / 2).succ)) =
    #(filter (fun x => x.1 * q ≤ x.2 * p) (Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ))
x : ℕ × ℕ
hx : x ∈ Ico 1 (p / 2).succ ×ˢ Ico 1 (q / 2).succ
hpq : x.2 * p ≤ x.1 * q
hqp : x.1 * q ≤ x.2 * p
hxp : x.1 < p
⊢ False