from __future__ import annotations

import argparse
import json
import os
import re
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any

_MPL_CACHE = Path(__file__).resolve().parents[1] / ".mpl-cache"
_MPL_CACHE.mkdir(parents=True, exist_ok=True)
os.environ.setdefault("MPLCONFIGDIR", str(_MPL_CACHE))

SCRIPT_DIR = Path(__file__).resolve().parent
if str(SCRIPT_DIR) not in sys.path:
    sys.path.insert(0, str(SCRIPT_DIR))
for dep_dir in [SCRIPT_DIR / ".pydeps", SCRIPT_DIR.parent / ".pydeps", Path.cwd() / ".pydeps"]:
    if dep_dir.exists() and str(dep_dir) not in sys.path:
        sys.path.insert(0, str(dep_dir))

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
import pymysql
import yaml

from runtime_config import add_config_source_args, load_runtime_config


TABLE_NAME = "cep_live_goods_effect"


@dataclass(frozen=True)
class ReportMonth:
    year: int
    month: int

    @property
    def ym(self) -> str:
        return f"{self.year}-{self.month:02d}"


def parse_month_number(raw: Any) -> int:
    if raw is None:
        return 1
    if isinstance(raw, int):
        return min(12, max(1, raw))
    text = str(raw).strip()
    m = re.search(r"(1[0-2]|0?[1-9])", text)
    if m:
        return int(m.group(1))
    return 1


def resolve_report_month(args: argparse.Namespace, config: dict[str, Any]) -> ReportMonth:
    report_cfg = config.get("report", {})
    year = int(args.report_year or report_cfg.get("year", 2026))
    month = parse_month_number(args.report_month or report_cfg.get("month_cn", "1"))
    return ReportMonth(year=year, month=month)


def pick_font() -> str:
    candidates = [
        r"C:\Windows\Fonts\msyh.ttc",
        r"C:\Windows\Fonts\simhei.ttf",
        r"C:\Windows\Fonts\simsun.ttc",
    ]
    for p in candidates:
        if Path(p).exists():
            return p
    return "sans-serif"


def fetch_category_data(conn: pymysql.connections.Connection, month: ReportMonth) -> list[dict[str, Any]]:
    sql = f"""
SELECT
  COALESCE(NULLIF(TRIM(category_level_1), ''), '未分类') AS level1,
  COALESCE(NULLIF(TRIM(category_level_3), ''), '未分类') AS level3,
  SUM(IFNULL(item_count_total, 0)) AS item_count
FROM `{TABLE_NAME}`
WHERE data_month = %s
GROUP BY level1, level3
HAVING SUM(IFNULL(item_count_total, 0)) > 0
"""
    with conn.cursor() as cur:
        cur.execute(sql, (month.ym,))
        return cur.fetchall() or []


def pct(v: float, total: float) -> float:
    if total <= 0:
        return 0.0
    return v / total


def fmt_pct(v: float) -> str:
    return f"{round(v * 100):.0f}%"


def render_chart(rows: list[dict[str, Any]], output_path: Path) -> dict[str, Any]:
    if not rows:
        raise RuntimeError("No data found for chart rendering.")

    level1_totals: dict[str, float] = {}
    for r in rows:
        l1 = str(r["level1"])
        level1_totals[l1] = level1_totals.get(l1, 0.0) + float(r["item_count"] or 0)

    level1_sorted = sorted(level1_totals.items(), key=lambda x: x[1], reverse=True)
    l1_labels = [x[0] for x in level1_sorted]
    l1_values = [x[1] for x in level1_sorted]
    l1_total = sum(l1_values)
    top_l1 = l1_labels[0]
    top_l1_share = pct(l1_values[0], l1_total)

    # 二级/三级分类按全量数据汇总（不限制在一级最大类），避免遗漏如挂件等品类。
    level3_totals: dict[str, float] = {}
    for r in rows:
        l3 = str(r["level3"])
        level3_totals[l3] = level3_totals.get(l3, 0.0) + float(r["item_count"] or 0)
    level3_sorted = sorted(level3_totals.items(), key=lambda x: x[1], reverse=True)
    l3_total = sum(v for _, v in level3_sorted)

    main_l3 = level3_sorted[:5]
    tail_l3 = level3_sorted[5:]

    font_name = pick_font()
    plt.rcParams["font.sans-serif"] = [font_name, "Microsoft YaHei", "SimHei", "Arial Unicode MS"]
    plt.rcParams["axes.unicode_minus"] = False

    fig = plt.figure(figsize=(12.8, 5.3), dpi=200, facecolor="#e7e7e7")
    ax1 = fig.add_axes([0.05, 0.14, 0.27, 0.72], facecolor="#e7e7e7")
    ax2 = fig.add_axes([0.31, 0.14, 0.33, 0.72], facecolor="#e7e7e7")
    ax3 = fig.add_axes([0.72, 0.19, 0.24, 0.56], facecolor="#e7e7e7")

    c1 = ["#6f76e8", "#e38bcb", "#8db2f5", "#9a8fe8", "#f2a66a", "#8fd3bd", "#c4c4c4"]
    ax1.pie(
        l1_values,
        labels=[f"{k}\n{fmt_pct(pct(v, l1_total))}" if pct(v, l1_total) >= 0.03 else "" for k, v in level1_sorted],
        colors=c1[: len(l1_values)],
        startangle=90,
        counterclock=False,
        wedgeprops={"width": 0.28, "edgecolor": "#f0f0f0", "linewidth": 0.6},
        labeldistance=1.12,
        textprops={"fontsize": 9},
    )
    ax1.set_title("一级分类", fontsize=12, pad=8)
    ax1.text(0, 0.05, top_l1, ha="center", va="center", fontsize=11, color="#333333")
    ax1.text(0, -0.12, fmt_pct(top_l1_share), ha="center", va="center", fontsize=11, color="#333333")
    ax1.axis("equal")

    c2 = ["#4e83c3", "#d66566", "#8eb4e3", "#74b5ad", "#bda6e6", "#f2b38f", "#d8c87d", "#9cc9dd"]
    l3_labels = [k for k, _ in main_l3]
    l3_values = [v for _, v in main_l3]
    labels = [f"{k} {fmt_pct(pct(v, l3_total))}" for k, v in main_l3]
    ax2.pie(
        l3_values,
        labels=labels,
        colors=c2[: len(l3_values)],
        startangle=270,
        counterclock=False,
        wedgeprops={"edgecolor": "#f0f0f0", "linewidth": 0.6},
        labeldistance=1.05,
        textprops={"fontsize": 9},
    )
    ax2.set_title("三级分类", fontsize=12, pad=8)
    ax2.axis("equal")

    if tail_l3:
        tail_items = tail_l3[:10]
        colors_tail = ["#6b8b37", "#5f2d7a", "#2e6ea3", "#ba5a2a", "#7f7f7f", "#a57ab5", "#6e9b8a", "#b38f5a", "#7092be", "#c0504d"]
        n = len(tail_items)
        for i, (lab, val) in enumerate(tail_items):
            y = n - 1 - i
            ax3.barh([y], [1.0], color=colors_tail[i % len(colors_tail)], height=0.78)
            ax3.text(1.06, y, f"{lab}, {fmt_pct(pct(val, l3_total))}", va="center", fontsize=8, color="#333333")
        ax3.set_xlim(0, 1.95)
        ax3.set_ylim(-0.8, max(0.8, n - 0.2))
        ax3.axis("off")
        fig.add_artist(Line2D([0.62, 0.72], [0.47, 0.58], transform=fig.transFigure, color="#9f9f9f", linewidth=1))
        fig.add_artist(Line2D([0.62, 0.72], [0.41, 0.30], transform=fig.transFigure, color="#9f9f9f", linewidth=1))
    else:
        ax3.axis("off")

    output_path.parent.mkdir(parents=True, exist_ok=True)
    fig.savefig(output_path, dpi=200, facecolor=fig.get_facecolor(), bbox_inches="tight")
    plt.close(fig)

    return {
        "top_level1": top_l1,
        "top_level1_count": int(l1_values[0]),
        "top_level1_share": round(top_l1_share, 4),
        "level1_count": len(level1_sorted),
        "level3_count": len(level3_sorted),
        "total_item_count": int(l1_total),
        "main_level3": [{"name": k, "value": int(v)} for k, v in main_l3],
        "tail_level3": [{"name": k, "value": int(v)} for k, v in tail_l3],
    }


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Generate goods-effect pivot chart from cep_live_goods_effect.")
    add_config_source_args(parser, default_config=Path(r"D:\projects\skills\vip-report\config.yaml"))
    parser.add_argument("--report-year", type=int, default=0, help="Override report year")
    parser.add_argument("--report-month", default="", help="Override report month")
    return parser.parse_args()


def main() -> None:
    args = parse_args()
    config_path = Path(args.config or r"D:\projects\skills\vip-report\config.yaml").resolve()
    config = load_runtime_config(args, default_config=config_path)
    report_month = resolve_report_month(args, config)

    mysql_cfg = config["mysql"]
    mysql_user = mysql_cfg.get("username") or mysql_cfg.get("user")
    conn = pymysql.connect(
        host=mysql_cfg["host"],
        port=int(mysql_cfg["port"]),
        user=mysql_user,
        password=mysql_cfg["password"],
        database=mysql_cfg["database"],
        charset="utf8mb4",
        cursorclass=pymysql.cursors.DictCursor,
    )
    try:
        rows = fetch_category_data(conn, report_month)
    finally:
        conn.close()

    vip_workdir = Path(config["paths"]["workdir"]).resolve()
    asset_dir = vip_workdir / "assets" / "cep-live-goods-effect"
    data_dir = vip_workdir / "data" / "cep-live-goods-effect"
    image_path = asset_dir / "cep-live-goods-effect-pivot.png"
    operations_path = vip_workdir / "render-ops.cep-live-goods-effect-pivot.live.json"
    manifest_path = data_dir / "cep-live-goods-effect-pivot.live.json"

    metrics = render_chart(rows, image_path)

    operations = {"replace_text": [], "replace_images": []}
    operations_path.write_text(json.dumps(operations, ensure_ascii=False, indent=2), encoding="utf-8")

    manifest = {
        "source": {"table": TABLE_NAME, "month": report_month.ym, "config_path": str(config_path)},
        "metrics": metrics,
        "asset_path": str(image_path),
        "operations_path": str(operations_path),
    }
    manifest_path.parent.mkdir(parents=True, exist_ok=True)
    manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")

    print(
        json.dumps(
            {"image_path": str(image_path), "manifest_path": str(manifest_path), "operations_path": str(operations_path)},
            ensure_ascii=False,
        )
    )


if __name__ == "__main__":
    main()
