#!/usr/bin/env python3
"""Diagnose why ref A + cpp b works but cpp A + cpp b fails despite tiny A diff."""

from __future__ import annotations

from pathlib import Path

import numpy as np
from scipy import sparse
from scipy.sparse.linalg import spsolve

ROOT = Path(__file__).resolve().parents[2]
REF = ROOT / "三维matlab代码/2023-2-端口激励问题(四面体网格)/OutFile_fem4_ab"
CPP = ROOT / "3D opticsfem-master/port/Release/OutFile_fem4"
PCOEF = 2.69467866496258e15


def parse_av_line(s: str) -> complex:
    s = s.strip()
    if s.startswith("("):
        body = s[1:-1]
        a, b = body.split(",", 1)
        return complex(float(a), float(b))
    return complex(float(s))


def load_coo(prefix: Path) -> sparse.csr_matrix:
    ai = np.loadtxt(prefix / "Ai.txt", dtype=np.int64)
    aj = np.loadtxt(prefix / "Aj.txt", dtype=np.int64)
    av = [parse_av_line(line) for line in (prefix / "Av.txt").read_text(encoding="utf-8", errors="ignore").splitlines() if line.strip()]
    return sparse.coo_matrix((av, (ai, aj)), shape=(79616, 79616)).tocsr()


def load_b(prefix: Path) -> np.ndarray:
    return np.loadtxt(prefix / "Bv_real.txt") + 1j * np.loadtxt(prefix / "Bv_imag.txt")


def main() -> int:
    print("=== Load matrices ===")
    A_ref = load_coo(REF)
    A_cpp = load_coo(CPP)
    b_ref = load_b(REF)
    b_cpp = load_b(CPP)

    print(f"A_ref nnz={A_ref.nnz}, A_cpp nnz={A_cpp.nnz}")
    print(f"indices equal: {np.array_equal(A_ref.indices, A_cpp.indices) and np.array_equal(A_ref.indptr, A_cpp.indptr)}")
    ddata = np.abs(A_ref.data - A_cpp.data)
    print(f"A.data max|diff|={ddata.max():.6g}, mean|diff|={ddata.mean():.6g}")
    print(f"A.data rel max|diff|={ddata.max()/max(np.abs(A_ref.data).max(),1e-30):.6g}")
    print(f"b max|diff|={np.abs(b_ref-b_cpp).max():.6g}, count>1e-6={(np.abs(b_ref-b_cpp)>1e-6).sum()}")

    # duplicate (i,j) in raw COO before CSR merge?
    ai = np.loadtxt(REF / "Ai.txt", dtype=np.int64)
    aj = np.loadtxt(REF / "Aj.txt", dtype=np.int64)
    keys = np.column_stack([ai, aj])
    uniq, counts = np.unique(keys, axis=0, return_counts=True)
    ndup = int((counts > 1).sum())
    print(f"raw COO duplicate (i,j) groups: {ndup}")

    print("\n=== CRITICAL: same b_cpp, two A ===")
    x_refA = spsolve(A_ref, b_cpp)
    x_cppA = spsolve(A_cpp, b_cpp)
    rel = np.linalg.norm(x_refA - x_cppA) / np.linalg.norm(x_refA)
    print(f"||x_refA - x_cppA|| / ||x_refA|| = {rel:.6g}")
    print(f"x_refA  S11={x_refA[-2]/PCOEF}  S21={x_refA[-1]/PCOEF}")
    print(f"x_cppA  S11={x_cppA[-2]/PCOEF}  S21={x_cppA[-1]/PCOEF}")

    print("\n=== Same A_ref, two b ===")
    x1 = spsolve(A_ref, b_ref)
    x2 = spsolve(A_ref, b_cpp)
    print(f"||x(b_ref)-x(b_cpp)||/||x(b_ref)|| = {np.linalg.norm(x1-x2)/np.linalg.norm(x1):.6g}")
    print(f"x(b_ref) S11={x1[-2]/PCOEF}")
    print(f"x(b_cpp) S11={x2[-2]/PCOEF}")

    print("\n=== C++ exported X ===")
    x_exp = np.loadtxt(CPP / "X_real.txt") + 1j * np.loadtxt(CPP / "X_imag.txt")
    free_len = 79616
    x_red = x_exp  # reduced? export is full 83350
    # map: C++ exports full _mX; reduced solve is 79616
    print(f"X size={x_exp.size}, e1={x_exp[83348]/PCOEF}, e2={x_exp[83349]/PCOEF}")
    print(f"||x_cppA - x_exp[?]|| : reduced x vs full X needs free map")

    # Where do A values differ most (relative)?
    mask = ddata > 0
    if mask.any():
        rel_d = ddata[mask] / np.maximum(np.abs(A_ref.data[mask]), 1e-30)
        top = np.argsort(-rel_d)[:10]
        print("\nTop relative A diffs (CSR data index):")
        for i in top:
            print(f"  idx={i} rel={rel_d[i]:.3g} abs={ddata[i]:.3g} ref={A_ref.data[i]:.6g} cpp={A_cpp.data[i]:.6g}")

    # Port columns 79614,79615
    for col in [79614, 79615]:
        dcol = (A_ref[:, col] - A_cpp[:, col]).toarray().ravel()
        nz = np.abs(dcol) > 0
        print(f"\nPort col {col}: nnz diff={nz.sum()}, max|diff|={np.abs(dcol).max():.3g}")

    return 0


if __name__ == "__main__":
    raise SystemExit(main())
