from __future__ import annotations

import argparse
import calendar
import json
import re
import sys
from dataclasses import dataclass
from decimal import Decimal
from pathlib import Path
from typing import Any

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))

from PIL import Image, ImageDraw, ImageFont
import pymysql
import yaml

from runtime_config import add_config_source_args, load_runtime_config


TABLE_NAME = "cep_live_session_data"


@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 to_decimal(value: Any) -> Decimal:
    if value is None:
        return Decimal("0")
    try:
        return Decimal(str(value))
    except Exception:
        return Decimal("0")


def load_font(size: int, *, bold: bool = False) -> ImageFont.ImageFont:
    candidates: list[str] = []
    if bold:
        candidates.extend([r"C:\Windows\Fonts\msyhbd.ttc", r"C:\Windows\Fonts\arialbd.ttf"])
    candidates.extend([r"C:\Windows\Fonts\msyh.ttc", r"C:\Windows\Fonts\arial.ttf", r"C:\Windows\Fonts\simsun.ttc"])
    for p in candidates:
        try:
            return ImageFont.truetype(p, size=size)
        except OSError:
            continue
    return ImageFont.load_default()


def fetch_daily_data(conn: pymysql.connections.Connection, report_month: ReportMonth) -> dict[str, tuple[Decimal, Decimal]]:
    sql = f"""
SELECT
  biz_date,
  SUM(IFNULL(live_period_sales_total, 0)) AS sales_total,
  SUM(IFNULL(purchasers_total, 0)) AS purchasers_total,
  SUM(IFNULL(accumulated_viewers, 0)) AS viewers_total
FROM `{TABLE_NAME}`
WHERE data_month = %s
GROUP BY biz_date
ORDER BY biz_date
"""
    out: dict[str, tuple[Decimal, Decimal]] = {}
    with conn.cursor() as cur:
        cur.execute(sql, (report_month.ym,))
        rows = cur.fetchall() or []
    for row in rows:
        k = str(row.get("biz_date"))
        s = to_decimal(row.get("sales_total"))
        p = to_decimal(row.get("purchasers_total"))
        v = to_decimal(row.get("viewers_total"))
        c = (p / v) if v > 0 else Decimal("0")
        out[k] = (s, c)
    return out


def build_series(report_month: ReportMonth, daily_map: dict[str, tuple[Decimal, Decimal]]) -> tuple[list[str], list[float], list[float]]:
    _, days = calendar.monthrange(report_month.year, report_month.month)
    labels: list[str] = []
    sales: list[float] = []
    convs: list[float] = []
    for d in range(1, days + 1):
        key = f"{report_month.year}-{report_month.month:02d}-{d:02d}"
        s, c = daily_map.get(key, (Decimal("0"), Decimal("0")))
        labels.append(f"{report_month.month}月{d}日")
        sales.append(float(s))
        convs.append(float(c))
    return labels, sales, convs


def draw_center(draw: ImageDraw.ImageDraw, box: tuple[int, int, int, int], text: str, font: ImageFont.ImageFont, fill: str) -> None:
    x0, y0, x1, y1 = box
    bb = draw.textbbox((0, 0), text, font=font)
    w = bb[2] - bb[0]
    h = bb[3] - bb[1]
    draw.text((x0 + (x1 - x0 - w) / 2, y0 + (y1 - y0 - h) / 2), text, font=font, fill=fill)


def render_chart(output_path: Path, labels: list[str], sales: list[float], convs: list[float]) -> None:
    # High-res output for clarity
    scale = 2
    w, h = 886 * scale, 201 * scale
    img = Image.new("RGB", (w, h), "#e7e7e7")
    draw = ImageDraw.Draw(img)

    font_title = load_font(14 * scale, bold=True)
    font_legend = load_font(9 * scale, bold=True)
    font_axis = load_font(8 * scale, bold=False)
    font_x = load_font(8 * scale, bold=False)

    left, top, right, bottom = 58 * scale, 54 * scale, 800 * scale, 160 * scale
    chart_h = bottom - top
    chart_w = right - left

    draw_center(draw, (0, 8 * scale, w, 26 * scale), "直播间销售额&渗透率", font_title, "#222222")

    legend_y = 34 * scale
    draw.rectangle((415 * scale, legend_y, 428 * scale, legend_y + 6 * scale), fill="#787ee6")
    draw.text((431 * scale, legend_y - 2 * scale), "销售额", fill="#444444", font=font_legend)
    draw.line((469 * scale, legend_y + 3 * scale, 482 * scale, legend_y + 3 * scale), fill="#f08ad9", width=2 * scale)
    draw.text((485 * scale, legend_y - 2 * scale), "渗透率", fill="#444444", font=font_legend)

    max_sales = max(sales) if sales else 0.0
    y_max = 30000 if max_sales <= 30000 else (int(max_sales / 5000) + 1) * 5000
    if y_max <= 0:
        y_max = 30000

    ticks_left = [0, y_max // 3, (y_max * 2) // 3, y_max]
    for t in ticks_left:
        y = bottom - int((t / y_max) * chart_h)
        draw.line((left, y, right, y), fill="#c6c6c6", width=scale)
        txt = str(int(t))
        bb = draw.textbbox((0, 0), txt, font=font_axis)
        draw.text((left - (bb[2] - bb[0]) - 8 * scale, y - 5 * scale), txt, fill="#444444", font=font_axis)

    ticks_right = [0.0, 0.05, 0.10, 0.15]
    for t in ticks_right:
        y = bottom - int((t / 0.15) * chart_h)
        txt = f"{t*100:.2f}%"
        draw.text((right + 6 * scale, y - 5 * scale), txt, fill="#444444", font=font_axis)

    n = len(labels)
    if n <= 0:
        output_path.parent.mkdir(parents=True, exist_ok=True)
        img.save(output_path)
        return

    step = chart_w / n
    bar_w = max(6 * scale, int(step * 0.36))

    for i, val in enumerate(sales):
        cx = left + int((i + 0.5) * step)
        bh = int((val / y_max) * chart_h) if y_max > 0 else 0
        draw.rectangle((cx - bar_w // 2, bottom - bh, cx + bar_w // 2, bottom), fill="#787ee6")

    pts: list[tuple[int, int]] = []
    for i, c in enumerate(convs):
        cx = left + int((i + 0.5) * step)
        cy = bottom - int((c / 0.15) * chart_h) if c > 0 else bottom
        pts.append((cx, cy))
    if len(pts) >= 2:
        draw.line(pts, fill="#f08ad9", width=2 * scale)

    for i, lab in enumerate(labels):
        cx = left + int((i + 0.5) * step)
        tmp = Image.new("RGBA", (90, 30), (0, 0, 0, 0))
        td = ImageDraw.Draw(tmp)
        td.text((0, 0), lab, fill="#444444", font=font_x)
        rot = tmp.rotate(90, expand=True)
        img.paste(rot, (cx - rot.width // 2, bottom + 4 * scale), rot)

    output_path.parent.mkdir(parents=True, exist_ok=True)
    img.save(output_path)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Generate livestream sales & penetration trend chart from cep_live_session_data.")
    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")
    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:
        daily_map = fetch_daily_data(conn, report_month)
    finally:
        conn.close()

    labels, sales, convs = build_series(report_month, daily_map)

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

    render_chart(image_path, labels, sales, convs)

    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)},
        "series": {"days": len(labels), "sales_sum": sum(sales), "max_sales": max(sales) if sales else 0.0},
        "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(
            {
                "operations_path": str(operations_path),
                "manifest_path": str(manifest_path),
                "image_path": str(image_path),
            },
            ensure_ascii=False,
        )
    )


if __name__ == "__main__":
    main()
