from __future__ import annotations

import csv
import hashlib
import json
import math
import statistics
import urllib.request
from collections import Counter
from pathlib import Path

import matplotlib.pyplot as plt
from matplotlib import font_manager

ROOT = Path(__file__).resolve().parent
PRIVATE = ROOT / "private"
OUTPUTS = ROOT / "outputs"
FIGURES = OUTPUTS / "figures"
SOURCE_URL = "https://www.afna.gov.tw/redirect_file.php?theme=web_structure&id=31187"


def number(value: str) -> float | None:
    value = value.strip().replace(",", "")
    if not value or value == "N/A":
        return None
    return float(value)


def gini(values: list[float]) -> float:
    ordered = sorted(values)
    n = len(ordered)
    total = sum(ordered)
    return 2 * sum((i + 1) * x for i, x in enumerate(ordered)) / (n * total) - (n + 1) / n


def pct_change(start: float, end: float) -> float:
    return (end / start - 1) * 100


def setup_font() -> None:
    candidates = [
        ROOT.parent.parent / "public" / "fonts" / "NotoSansTC-Regular.ttf",
        Path("C:/Windows/Fonts/msjh.ttc"),
    ]
    for candidate in candidates:
        if candidate.exists():
            font_manager.fontManager.addfont(str(candidate))
            plt.rcParams["font.family"] = font_manager.FontProperties(fname=str(candidate)).get_name()
            break
    plt.rcParams["axes.unicode_minus"] = False
    plt.rcParams["figure.dpi"] = 160


def read_membership() -> list[dict[str, int]]:
    with (ROOT / "membership-trend.csv").open(encoding="utf-8", newline="") as handle:
        return [{key: int(value) for key, value in row.items()} for row in csv.DictReader(handle)]


def read_statutory() -> list[dict[str, str]]:
    with (ROOT / "statutory-functions.csv").open(encoding="utf-8", newline="") as handle:
        return list(csv.DictReader(handle))


def download_credit() -> Path:
    PRIVATE.mkdir(exist_ok=True)
    target = PRIVATE / "11412-farmers-association-credit.csv"
    if not target.exists():
        request = urllib.request.Request(SOURCE_URL, headers={"User-Agent": "SHWRP-2026-018-research/1.0"})
        with urllib.request.urlopen(request, timeout=60) as response:
            target.write_bytes(response.read())
    return target


def read_credit() -> tuple[list[dict[str, float | str]], str]:
    source = download_credit()
    raw = source.read_bytes()
    rows = list(csv.reader(raw.decode("utf-8").splitlines()))
    records: list[dict[str, float | str]] = []
    for row in rows[5:]:
        if not row or not row[0].strip().isdigit():
            continue
        records.append({
            "source_code": row[0].strip(),
            "deposits_ntd_thousand": number(row[2]),
            "loans_ntd_thousand": number(row[3]),
            "net_worth_ntd_thousand": number(row[4]),
            "profit_before_tax_ntd_thousand": number(row[5]),
            "npl_old_ntd_thousand": number(row[6]),
            "npl_new_ntd_thousand": number(row[7]),
            "npl_ratio_old_percent": number(row[8]),
            "npl_ratio_new_percent": number(row[9]),
            "loan_loss_reserve_ntd_thousand": number(row[10]),
            "coverage_old_percent": number(row[11]),
            "coverage_new_percent": number(row[12]),
            "bis_percent": number(row[13]),
        })
    return records, hashlib.sha256(raw).hexdigest()


def write_derived(records: list[dict[str, float | str]]) -> None:
    fields = [key for key in records[0] if key != "source_code"]
    with (ROOT / "credit-departments-derived.csv").open("w", encoding="utf-8", newline="") as handle:
        writer = csv.DictWriter(handle, fieldnames=["case_id", *fields])
        writer.writeheader()
        for index, record in enumerate(records, start=1):
            writer.writerow({"case_id": f"FA{index:03d}", **{field: record[field] for field in fields}})


def savefig(name: str) -> None:
    plt.tight_layout()
    plt.savefig(FIGURES / name, bbox_inches="tight", facecolor="#fffdf9")
    plt.close()


def create_figures(members: list[dict[str, int]], records: list[dict[str, float | str]], statutory: list[dict[str, str]]) -> None:
    setup_font()
    FIGURES.mkdir(parents=True, exist_ok=True)
    years = [r["year"] for r in members]

    plt.figure(figsize=(9.2, 5.3))
    plt.plot(years, [r["regular_members"] / 1_000_000 for r in members], marker="o", linewidth=2.4, label="正會員")
    plt.plot(years, [r["supporting_members"] / 1_000_000 for r in members], marker="o", linewidth=2.0, label="贊助會員")
    plt.ylabel("人數（百萬人）")
    plt.title("圖1　2015–2024年農會會員數變化")
    plt.grid(alpha=0.2)
    plt.legend(frameon=False)
    savefig("figure-1-membership-trend.png")

    start = members[0]
    plt.figure(figsize=(9.2, 5.2))
    series = {
        "總會員": [r["total_members"] / start["total_members"] * 100 for r in members],
        "正會員": [r["regular_members"] / start["regular_members"] * 100 for r in members],
        "農事小組": [r["farming_groups"] / start["farming_groups"] * 100 for r in members],
    }
    for label, values in series.items():
        plt.plot(years, values, marker="o", linewidth=2.2, label=label)
    plt.axhline(100, color="#999999", linewidth=1)
    plt.ylabel("2015年＝100")
    plt.title("圖2　會員與基層組織變化指數")
    plt.grid(alpha=0.2)
    plt.legend(frameon=False)
    savefig("figure-2-membership-index.png")

    total_deposits = sum(float(r["deposits_ntd_thousand"]) for r in records) / 1_000_000_000
    total_loans = sum(float(r["loans_ntd_thousand"]) for r in records) / 1_000_000_000
    total_net_worth = sum(float(r["net_worth_ntd_thousand"]) for r in records) / 1_000_000_000
    total_profit = sum(float(r["profit_before_tax_ntd_thousand"]) for r in records) / 1_000_000_000
    plt.figure(figsize=(9.2, 5.2))
    values = [total_deposits, total_loans, total_net_worth, total_profit]
    labels = ["存款", "放款", "淨值", "稅前損益"]
    bars = plt.bar(labels, values, color=["#704123", "#9a6a45", "#53735c", "#759b7d"])
    plt.yscale("log")
    plt.ylabel("新臺幣兆元（對數尺度）")
    plt.title("圖3　2025年底283家農會信用部的承接規模")
    for bar, value in zip(bars, values):
        plt.text(bar.get_x() + bar.get_width() / 2, value * 1.12, f"{value:.3f}兆", ha="center", fontsize=9)
    savefig("figure-3-credit-scale.png")

    deposits = [float(r["deposits_ntd_thousand"]) / 1_000_000 for r in records]
    loans = [float(r["loans_ntd_thousand"]) / 1_000_000 for r in records]
    plt.figure(figsize=(8.1, 6.1))
    plt.scatter(deposits, loans, alpha=0.58, s=27, color="#53735c", edgecolors="none")
    plt.xlabel("存款（十億元）")
    plt.ylabel("放款（十億元）")
    plt.title("圖4　農會信用部存放款規模分布（匿名）")
    plt.grid(alpha=0.18)
    savefig("figure-4-credit-scatter.png")

    npl = [float(r["npl_ratio_old_percent"]) for r in records]
    bis = [float(r["bis_percent"]) for r in records]
    plt.figure(figsize=(8.1, 5.8))
    plt.scatter(npl, bis, alpha=0.58, s=26, color="#9a6a45", edgecolors="none")
    plt.axvline(1, color="#777777", linestyle="--", linewidth=1, label="逾放比率1%")
    plt.xlabel("狹義逾放比率（%）")
    plt.ylabel("淨值占風險性資產比率（%）")
    plt.title("圖5　信用部風險指標分布（匿名描述）")
    plt.grid(alpha=0.18)
    plt.legend(frameon=False)
    savefig("figure-5-credit-risk.png")

    counts = Counter(r["function_domain"] for r in statutory)
    labels, values = zip(*sorted(counts.items(), key=lambda item: item[1]))
    plt.figure(figsize=(9.2, 5.8))
    bars = plt.barh(labels, values, color="#704123")
    plt.xlabel("法定任務項數（不代表重要性權重）")
    plt.title("圖6　農會法第4條21項任務之功能分類")
    for bar, value in zip(bars, values):
        plt.text(value + 0.08, bar.get_y() + bar.get_height() / 2, str(value), va="center")
    plt.xlim(0, max(values) + 1)
    savefig("figure-6-statutory-domains.png")


def main() -> None:
    members = read_membership()
    statutory = read_statutory()
    credit, sha256 = read_credit()
    write_derived(credit)
    create_figures(members, credit, statutory)

    deposits = [float(r["deposits_ntd_thousand"]) for r in credit]
    loans = [float(r["loans_ntd_thousand"]) for r in credit]
    profits = [float(r["profit_before_tax_ntd_thousand"]) for r in credit]
    npl = [float(r["npl_ratio_old_percent"]) for r in credit]
    bis = [float(r["bis_percent"]) for r in credit]
    results = {
        "membership": {
            "period": [members[0]["year"], members[-1]["year"]],
            "regular_change_count": members[-1]["regular_members"] - members[0]["regular_members"],
            "regular_change_percent": round(pct_change(members[0]["regular_members"], members[-1]["regular_members"]), 2),
            "regular_cagr_percent": round(((members[-1]["regular_members"] / members[0]["regular_members"]) ** (1 / 9) - 1) * 100, 2),
            "total_change_percent": round(pct_change(members[0]["total_members"], members[-1]["total_members"]), 2),
            "supporting_change_percent": round(pct_change(members[0]["supporting_members"], members[-1]["supporting_members"]), 2),
            "farming_groups_change_percent": round(pct_change(members[0]["farming_groups"], members[-1]["farming_groups"]), 2),
        },
        "credit_2025": {
            "institutions": len(credit),
            "source_sha256": sha256,
            "deposits_ntd_trillion": round(sum(deposits) / 1_000_000_000, 3),
            "loans_ntd_trillion": round(sum(loans) / 1_000_000_000, 3),
            "net_worth_ntd_billion": round(sum(float(r["net_worth_ntd_thousand"]) for r in credit) / 1_000_000, 3),
            "profit_before_tax_ntd_billion": round(sum(profits) / 1_000_000, 3),
            "loan_deposit_ratio_percent": round(sum(loans) / sum(deposits) * 100, 2),
            "median_deposits_ntd_billion": round(statistics.median(deposits) / 1_000_000, 3),
            "median_loans_ntd_billion": round(statistics.median(loans) / 1_000_000, 3),
            "median_profit_ntd_million": round(statistics.median(profits) / 1_000, 3),
            "median_npl_ratio_percent": round(statistics.median(npl), 2),
            "npl_ratio_le_1_count": sum(value <= 1 for value in npl),
            "npl_ratio_gt_3_count": sum(value > 3 for value in npl),
            "median_bis_percent": round(statistics.median(bis), 2),
            "deposit_gini": round(gini(deposits), 3),
            "loan_gini": round(gini(loans), 3),
            "top10_deposit_share_percent": round(sum(sorted(deposits, reverse=True)[:10]) / sum(deposits) * 100, 2),
            "all_positive_profit": all(value > 0 for value in profits),
        },
        "statutory": {
            "tasks": len(statutory),
            "domain_counts": dict(Counter(row["function_domain"] for row in statutory)),
        },
        "interpretive_rules": [
            "financial scale is not a causal estimate of farmer welfare",
            "statutory task counts are not importance weights",
            "substitution assessments are transparent researcher judgments, not observed treatment effects",
        ],
    }
    (ROOT / "analysis-results.json").write_text(json.dumps(results, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(json.dumps(results, ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()

