from __future__ import annotations

import csv
import json
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from matplotlib import font_manager
from matplotlib.patches import FancyBboxPatch


ROOT = Path(__file__).resolve().parent
OUTPUTS = ROOT / "outputs"
FIGURES = OUTPUTS / "figures"
FONT = ROOT / "assets" / "fonts" / "NotoSansTC-Regular.ttf"

REPORT_NUMBER = "SHWRP-2026-031"
DESIGN = {
    "independent_fields": 4,
    "fixed_plots_per_field": 20,
    "growth_stages": 3,
    "repeat_flight_stages": 2,
    "spad_readings_per_plot_visit": 15,
    "checkpoints_per_field": 5,
}


def configure_font() -> None:
    font_manager.fontManager.addfont(str(FONT))
    plt.rcParams["font.family"] = "Noto Sans TC"
    plt.rcParams["axes.unicode_minus"] = False


def save_figure(fig: plt.Figure, filename: str) -> None:
    fig.savefig(FIGURES / filename, dpi=220, bbox_inches="tight", facecolor="#fbf7ef")
    plt.close(fig)


def add_box(ax, xy, width, height, title, body, color="#f4e5d6", edge="#9a582d"):
    x, y = xy
    patch = FancyBboxPatch(
        (x, y), width, height,
        boxstyle="round,pad=0.02,rounding_size=0.025",
        linewidth=1.4, edgecolor=edge, facecolor=color,
    )
    ax.add_patch(patch)
    ax.text(x + width / 2, y + height * 0.68, title, ha="center", va="center", fontsize=12, fontweight="bold", color="#3d2417")
    ax.text(x + width / 2, y + height * 0.30, body, ha="center", va="center", fontsize=8.3, color="#5d4a40", linespacing=1.35)


def build_workload() -> pd.DataFrame:
    fields = DESIGN["independent_fields"]
    plots = fields * DESIGN["fixed_plots_per_field"]
    plot_dates = plots * DESIGN["growth_stages"]
    primary_flights = fields * DESIGN["growth_stages"]
    repeat_flights = fields * DESIGN["repeat_flight_stages"]
    total_flights = primary_flights + repeat_flights
    values = [
        ("獨立田區", fields, "田", "群聚推論單位"),
        ("固定2×2m樣區", plots, "區", "80個獨立空間單位"),
        ("樣區-日期配對", plot_dates, "筆", "重複觀測，不是240個獨立樣本"),
        ("SPAD葉片讀值", plot_dates * DESIGN["spad_readings_per_plot_visit"], "次", "每樣區15次取中位數"),
        ("主要航次", primary_flights, "航次", "4田×3生育期"),
        ("同日重飛航次", repeat_flights, "航次", "4田×2生育期"),
        ("總規劃航次", total_flights, "航次", "不含天候或QC失敗重飛"),
        ("獨立幾何檢核點", fields * DESIGN["checkpoints_per_field"], "點", "不可同時作GCP"),
    ]
    frame = pd.DataFrame(values, columns=["item", "planned_count", "unit", "interpretation"])
    frame.to_csv(OUTPUTS / "protocol-workload.csv", index=False, encoding="utf-8-sig")
    return frame


def build_capability_matrix() -> pd.DataFrame:
    rows = [
        ("Mavic 3M", 3, 3, 0, 3, 2, 3),
        ("Matrice 4E", 0, 3, 0, 2, 3, 2),
        ("Matrice 4T", 0, 2, 3, 2, 3, 1),
    ]
    columns = ["model", "作物反射率", "RGB測繪", "熱異常", "攜行性", "官方飛時", "本研究MVP適配"]
    frame = pd.DataFrame(rows, columns=columns)
    frame.to_csv(OUTPUTS / "model-capability-matrix.csv", index=False, encoding="utf-8-sig")
    return frame


def build_normalized_cost_grid() -> pd.DataFrame:
    annual_missions = [20, 50, 100, 200, 400]
    fixed_to_variable_ratios = [20, 50, 100]
    rows = []
    for ratio in fixed_to_variable_ratios:
        for missions in annual_missions:
            rows.append({
                "annual_missions": missions,
                "annual_fixed_cost_in_variable_mission_units": ratio,
                "normalized_cost_per_mission": round(1 + ratio / missions, 4),
                "interpretation": "1.0代表只有一次任務的變動成本；未使用新臺幣或假報價",
            })
    frame = pd.DataFrame(rows)
    frame.to_csv(OUTPUTS / "normalized-cost-sensitivity.csv", index=False, encoding="utf-8-sig")
    return frame


def figure_architecture() -> None:
    fig, ax = plt.subplots(figsize=(12, 4.8))
    fig.patch.set_facecolor("#fbf7ef")
    ax.set_xlim(0, 1)
    ax.set_ylim(0, 1)
    ax.axis("off")
    boxes = [
        (0.02, "1. 標準化航拍", "M3M RGB＋G/R/RE/NIR\n校正板、RTK、固定航線"),
        (0.22, "2. 影像品質閘門", "完整率、RTK、重疊\ncheckpoint與輻射QC"),
        (0.42, "3. 相對異常排序", "RGB / NDVI / NDRE\n只產生待查核熱點"),
        (0.62, "4. 盲態地面複核", "SPAD、近照、株高\n土壤水分與專業判讀"),
        (0.82, "5. 可追溯交付", "五項輸出＋不確定性\n任務receipt與停止條件"),
    ]
    for x, title, body in boxes:
        add_box(ax, (x, 0.25), 0.16, 0.48, title, body)
    for x in [0.185, 0.385, 0.585, 0.785]:
        ax.annotate("", xy=(x + 0.025, 0.49), xytext=(x - 0.005, 0.49), arrowprops=dict(arrowstyle="->", lw=1.8, color="#9a582d"))
    ax.text(0.5, 0.92, "最低可行產品不是自動診斷，而是可停止、可追溯的巡檢—複核流程", ha="center", fontsize=16, fontweight="bold", color="#3d2417")
    ax.text(0.5, 0.08, "任何一個品質、法規或地面驗證閘門失敗，該次輸出即不得升級為處置建議。", ha="center", fontsize=10, color="#73513c")
    save_figure(fig, "figure-1-mvp-architecture.png")


def figure_models(frame: pd.DataFrame) -> None:
    values = frame.drop(columns="model").to_numpy()
    fig, ax = plt.subplots(figsize=(10.5, 4.4))
    image = ax.imshow(values, cmap="YlOrBr", vmin=0, vmax=3, aspect="auto")
    ax.set_xticks(range(values.shape[1]), frame.columns[1:], rotation=25, ha="right")
    ax.set_yticks(range(values.shape[0]), frame["model"])
    for i in range(values.shape[0]):
        for j in range(values.shape[1]):
            ax.text(j, i, str(values[i, j]), ha="center", va="center", color="white" if values[i, j] >= 2 else "#3d2417", fontweight="bold")
    ax.set_title("DJI機型與本研究任務特徵的適配編碼（0=不具備；3=高度適配）", fontweight="bold", pad=14)
    ax.set_xlabel("研究者依官方規格編碼；不是產品品質、可靠度或市場排名")
    fig.colorbar(image, ax=ax, fraction=0.025, pad=0.04)
    fig.patch.set_facecolor("#fbf7ef")
    ax.set_facecolor("#fffdf9")
    save_figure(fig, "figure-2-aircraft-fit-matrix.png")


def figure_sampling() -> None:
    fig, ax = plt.subplots(figsize=(11, 6.2))
    ax.set_xlim(0, 11)
    ax.set_ylim(0, 7)
    ax.axis("off")
    ax.set_title("前瞻性MVP抽樣設計：4田、80固定樣區、3生育期", fontsize=16, fontweight="bold", pad=12)
    rng = np.random.default_rng(20260803)
    for field in range(4):
        x0 = 0.6 + (field % 2) * 5.3
        y0 = 3.7 if field < 2 else 0.6
        rect = FancyBboxPatch((x0, y0), 4.5, 2.3, boxstyle="round,pad=0.02", facecolor="#eaf2df", edgecolor="#66804b", lw=1.3)
        ax.add_patch(rect)
        points = rng.uniform([x0 + 0.25, y0 + 0.25], [x0 + 4.25, y0 + 2.05], size=(20, 2))
        ax.scatter(points[:, 0], points[:, 1], s=25, c="#7b3f20", alpha=0.9)
        ax.text(x0 + 0.15, y0 + 2.05, f"田區 F{field + 1}｜20固定樣區", fontsize=9.5, fontweight="bold", color="#304326")
    ax.text(5.5, 6.45, "每點同步：15次SPAD＋近地照片＋株高＋冠層＋3點土壤水分", ha="center", fontsize=10, color="#5d4a40")
    ax.text(5.5, 0.12, "三期重複形成240個樣區-日期觀測，但推論與交叉驗證以田區／樣區群聚處理，絕不把像素當作獨立樣本。", ha="center", fontsize=9.5, color="#73513c")
    save_figure(fig, "figure-3-sampling-design.png")


def figure_evidence_ladder() -> None:
    levels = [
        ("L1", "官方規格", "能拍到什麼\n不能證明田間效能"),
        ("L2", "既有文獻", "技術可能性\n不能直接轉移臺灣"),
        ("L3", "本v1.0方案", "預先鎖定樣本、模型\n門檻與停止條件"),
        ("L4", "單季pilot", "重複性與外田誤差\n仍非普遍確證"),
        ("L5", "跨季外部驗證", "不同田區、品種、天候\n才評估服務化"),
    ]
    fig, ax = plt.subplots(figsize=(11, 5.1))
    ax.set_xlim(0, 10)
    ax.set_ylim(0, 6)
    ax.axis("off")
    for index, (code, title, body) in enumerate(levels):
        x = 0.5 + index * 1.85
        y = 0.45 + index * 0.8
        rect = FancyBboxPatch((x, y), 1.55, 1.35, boxstyle="round,pad=0.03", facecolor=plt.cm.YlOrBr(0.25 + index * 0.13), edgecolor="#7b3f20")
        ax.add_patch(rect)
        ax.text(x + 0.78, y + 1.05, f"{code}｜{title}", ha="center", fontsize=10.5, fontweight="bold", color="#3d2417")
        ax.text(x + 0.78, y + 0.52, body, ha="center", va="center", fontsize=8.4, color="#4c3b31")
        if index < len(levels) - 1:
            ax.annotate("", xy=(x + 1.9, y + 1.15), xytext=(x + 1.58, y + 0.9), arrowprops=dict(arrowstyle="->", lw=1.5, color="#9a582d"))
    ax.set_title("可信度是逐層累積，不是購買設備後自動取得", fontsize=16, fontweight="bold")
    ax.text(5, 0.15, "本工作論文只完成L1-L3；L4與L5均為未來待驗證工作。", ha="center", fontsize=10, color="#7b3f20", fontweight="bold")
    save_figure(fig, "figure-4-evidence-ladder.png")


def figure_workload(frame: pd.DataFrame) -> None:
    display = frame.loc[frame["item"].isin(["固定2×2m樣區", "樣區-日期配對", "SPAD葉片讀值", "總規劃航次", "獨立幾何檢核點"])].copy()
    fig, ax = plt.subplots(figsize=(10.5, 5.2))
    bars = ax.barh(display["item"], display["planned_count"], color=["#8d5a3a", "#a76b3d", "#c9823f", "#d9aa68", "#7a9155"])
    ax.set_xscale("log")
    ax.set_xlabel("規劃數量（對數尺度；不同單位不可直接比較）")
    ax.set_title("最低可行田間研究的可稽核工作量", fontweight="bold", pad=12)
    ax.grid(axis="x", alpha=0.22)
    for bar, (_, row) in zip(bars, display.iterrows()):
        ax.text(bar.get_width() * 1.05, bar.get_y() + bar.get_height() / 2, f"{row['planned_count']:,} {row['unit']}", va="center", fontsize=9)
    fig.patch.set_facecolor("#fbf7ef")
    ax.set_facecolor("#fffdf9")
    save_figure(fig, "figure-5-protocol-workload.png")


def figure_cost(frame: pd.DataFrame) -> None:
    fig, ax = plt.subplots(figsize=(10.5, 5.2))
    for ratio, group in frame.groupby("annual_fixed_cost_in_variable_mission_units"):
        ax.plot(group["annual_missions"], group["normalized_cost_per_mission"], marker="o", linewidth=2, label=f"年度固定成本 = {ratio}個任務變動成本")
    ax.axhline(1, color="#65584f", linestyle="--", linewidth=1)
    ax.set_xlabel("每年完成且通過QC的任務數")
    ax.set_ylabel("每任務正規化成本")
    ax.set_title("任務密度如何攤薄固定成本：無新臺幣假價格的敏感度圖", fontweight="bold", pad=12)
    ax.legend(frameon=False)
    ax.grid(alpha=0.22)
    ax.set_ylim(bottom=0.9)
    fig.patch.set_facecolor("#fbf7ef")
    ax.set_facecolor("#fffdf9")
    save_figure(fig, "figure-6-normalized-cost-sensitivity.png")


def main() -> None:
    OUTPUTS.mkdir(parents=True, exist_ok=True)
    FIGURES.mkdir(parents=True, exist_ok=True)
    configure_font()
    workload = build_workload()
    capability = build_capability_matrix()
    cost = build_normalized_cost_grid()
    figure_architecture()
    figure_models(capability)
    figure_sampling()
    figure_evidence_ladder()
    figure_workload(workload)
    figure_cost(cost)

    results = {
        "schemaVersion": 1,
        "reportNumber": REPORT_NUMBER,
        "analysisType": "prospective protocol design and transparent normalized sensitivity analysis",
        "fieldDataCollected": False,
        "observedPerformanceMetrics": {},
        "designCounts": {
            "independentFields": DESIGN["independent_fields"],
            "independentFixedPlots": DESIGN["independent_fields"] * DESIGN["fixed_plots_per_field"],
            "plotDateRepeatedObservations": DESIGN["independent_fields"] * DESIGN["fixed_plots_per_field"] * DESIGN["growth_stages"],
            "plannedSpadReadings": DESIGN["independent_fields"] * DESIGN["fixed_plots_per_field"] * DESIGN["growth_stages"] * DESIGN["spad_readings_per_plot_visit"],
            "primaryFlights": DESIGN["independent_fields"] * DESIGN["growth_stages"],
            "repeatFlights": DESIGN["independent_fields"] * DESIGN["repeat_flight_stages"],
            "totalPlannedFlights": DESIGN["independent_fields"] * (DESIGN["growth_stages"] + DESIGN["repeat_flight_stages"]),
            "independentCheckpoints": DESIGN["independent_fields"] * DESIGN["checkpoints_per_field"],
        },
        "selectedAircraft": "DJI Mavic 3 Multispectral",
        "selectionReason": "Only compared DJI option combining 20 MP mechanical-shutter RGB with dedicated G/R/RE/NIR multispectral bands for paired crop-reflectance validation.",
        "primaryOutcome": "plot-level median SPAD as a chlorophyll proxy; not plant nitrogen concentration",
        "productOutputBoundary": "relative anomaly and ground-check priority only; no disease, nutrient, water-stress or pesticide prescription",
        "costModel": {
            "currencyValuesPresent": False,
            "formula": "annual cost=(acquisition-residual)/life + annual fixed + missions*variable mission cost",
            "requiredBeforeFinancialClaim": "three same-day written tax-inclusive quotes plus measured labor, travel, storage and QC time",
        },
        "figures": sorted(path.name for path in FIGURES.glob("*.png")),
    }
    (OUTPUTS / "analysis-results.json").write_text(json.dumps(results, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(json.dumps(results, ensure_ascii=False))


if __name__ == "__main__":
    main()
