#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
services/chart_generator_service.py
專業圖表生成服務

供日報 / 週報 / 月報 + Telegram 圖文通知使用。
所有圖表回傳 BytesIO（可直接傳 Telegram sendPhoto）。

圖表清單：
  price_trend_chart(sku, days)          — 單 SKU 30天 MOMO vs 競品價格趨勢
  category_revenue_chart(days)          — 品類業績橫條圖
  revenue_trend_chart(days)             — 每日業績折線圖（含 MoM 基準線）
  price_gap_distribution_chart(threats) — 競價威脅價差分佈
  monthly_overview_chart(months)        — 月份業績對比柱狀圖
  top_sku_heatmap(days)                 — TOP SKU 業績熱力圖
"""

import io
import logging
from datetime import datetime, timedelta
from typing import List, Optional, Dict, Any

logger = logging.getLogger(__name__)

# ── Matplotlib 無頭模式初始化 ────────────────────────────────────────────────
try:
    import matplotlib
    matplotlib.use("Agg")
    import matplotlib.pyplot as plt
    import matplotlib.dates as mdates
    import numpy as np
    _MPL_OK = True
except ImportError:
    _MPL_OK = False
    logger.warning("[Chart] matplotlib 未安裝，圖表功能停用")

# ── 視覺設定（深色商業風格） ──────────────────────────────────────────────────
_BG       = "#1a1a2e"
_PANEL    = "#16213e"
_ACCENT1  = "#e94560"   # 紅：MOMO / 警示
_ACCENT2  = "#0f3460"   # 深藍：競品
_ACCENT3  = "#533483"   # 紫：歷史基準
_GREEN    = "#00b4d8"   # 青綠：業績正成長
_YELLOW   = "#ffd166"   # 黃：中性 / 警示
_TEXT     = "#e0e0e0"
_SUBTEXT  = "#888888"
_GRID     = "#2a2a4a"

plt_params = {
    "figure.facecolor":  _BG,
    "axes.facecolor":    _PANEL,
    "axes.edgecolor":    _GRID,
    "axes.labelcolor":   _TEXT,
    "xtick.color":       _SUBTEXT,
    "ytick.color":       _SUBTEXT,
    "text.color":        _TEXT,
    "grid.color":        _GRID,
    "grid.linestyle":    "--",
    "grid.alpha":        0.5,
    "font.family":       ["DejaVu Sans", "sans-serif"],
    "font.size":         10,
}


def _apply_style():
    if _MPL_OK:
        plt.rcParams.update(plt_params)


def _fig_to_bytes(fig) -> Optional[bytes]:
    buf = io.BytesIO()
    fig.savefig(buf, format="png", dpi=150, bbox_inches="tight",
                facecolor=fig.get_facecolor())
    plt.close(fig)
    buf.seek(0)
    return buf.read()


def _unavailable() -> None:
    return None


# ═══════════════════════════════════════════════════════════════════════════════
# 資料讀取層（與 DB 解耦，各函式自行呼叫）
# ═══════════════════════════════════════════════════════════════════════════════

def _fetch_price_history(sku: str, days: int = 30) -> Dict[str, Any]:
    """讀取單 SKU 的 MOMO + 競品歷史價格"""
    try:
        from database.manager import get_session
        from sqlalchemy import text
        session = get_session()
        try:
            momo_rows = session.execute(text("""
                SELECT pr.timestamp::date AS dt, AVG(pr.price) AS price
                FROM price_records pr
                JOIN products p ON p.id = pr.product_id
                WHERE p.i_code = :sku
                  AND pr.timestamp >= NOW() - INTERVAL ':days days'
                GROUP BY dt ORDER BY dt
            """.replace(":days", str(days))), {"sku": sku}).fetchall()

            comp_rows = session.execute(text("""
                SELECT crawled_at::date AS dt, AVG(price) AS price
                FROM competitor_price_history
                WHERE sku = :sku
                  AND source = 'pchome'
                  AND crawled_at >= NOW() - INTERVAL ':days days'
                  AND COALESCE(match_score, 0) >= 0.76
                  AND COALESCE(tags, '[]'::jsonb) ? 'identity_v2'
                GROUP BY dt ORDER BY dt
            """.replace(":days", str(days))), {"sku": sku}).fetchall()

            return {
                "momo":  [(str(r[0]), float(r[1])) for r in momo_rows],
                "pchome":[(str(r[0]), float(r[1])) for r in comp_rows],
            }
        finally:
            session.close()
    except Exception as e:
        logger.warning("[Chart] price_history 讀取失敗: %s", e)
        return {"momo": [], "pchome": []}


def _fetch_daily_revenue(days: int = 30) -> List[Dict]:
    try:
        from database.manager import get_session
        from sqlalchemy import text
        session = get_session()
        try:
            rows = session.execute(text(f"""
                SELECT snapshot_date::date AS dt,
                       SUM(COALESCE("總業績"::numeric, 0)) AS revenue
                FROM daily_sales_snapshot
                WHERE snapshot_date::date >= CURRENT_DATE - {days}
                GROUP BY dt ORDER BY dt
            """)).fetchall()
            return [{"date": str(r[0]), "revenue": float(r[1] or 0)} for r in rows]
        finally:
            session.close()
    except Exception as e:
        logger.warning("[Chart] daily_revenue 讀取失敗: %s", e)
        return []


def _fetch_category_revenue(days: int = 7) -> List[Dict]:
    try:
        from database.manager import get_session
        from sqlalchemy import text
        session = get_session()
        try:
            rows = session.execute(text(f"""
                SELECT p.category,
                       SUM(COALESCE(s."總業績"::numeric, 0)) AS revenue
                FROM daily_sales_snapshot s
                JOIN products p ON p.name = s."商品名稱"
                WHERE s.snapshot_date::date >= CURRENT_DATE - {days}
                  AND p.status = 'ACTIVE'
                  AND p.category IS NOT NULL
                GROUP BY p.category
                ORDER BY revenue DESC LIMIT 12
            """)).fetchall()
            return [{"category": r[0] or "未分類", "revenue": float(r[1] or 0)} for r in rows]
        finally:
            session.close()
    except Exception as e:
        logger.warning("[Chart] category_revenue 讀取失敗: %s", e)
        return []


def _fetch_monthly_revenue(months: int = 6) -> List[Dict]:
    try:
        from database.manager import get_session
        from sqlalchemy import text
        session = get_session()
        try:
            rows = session.execute(text(f"""
                SELECT DATE_TRUNC('month', snapshot_date::date)::date AS mon,
                       SUM(COALESCE("總業績"::numeric, 0)) AS revenue
                FROM daily_sales_snapshot
                WHERE snapshot_date::date >= CURRENT_DATE - INTERVAL '{months} months'
                GROUP BY mon ORDER BY mon
            """)).fetchall()
            return [{"month": str(r[0])[:7], "revenue": float(r[1] or 0)} for r in rows]
        finally:
            session.close()
    except Exception as e:
        logger.warning("[Chart] monthly_revenue 讀取失敗: %s", e)
        return []


# ═══════════════════════════════════════════════════════════════════════════════
# 圖表函式
# ═══════════════════════════════════════════════════════════════════════════════

def price_trend_chart(sku: str, sku_name: str = "", days: int = 30) -> Optional[bytes]:
    """單 SKU：MOMO vs PChome 價格趨勢折線圖"""
    if not _MPL_OK:
        return _unavailable()
    data = _fetch_price_history(sku, days)
    if not data["momo"]:
        return None

    _apply_style()
    fig, ax = plt.subplots(figsize=(10, 4))
    fig.patch.set_facecolor(_BG)

    if data["momo"]:
        dates = [r[0] for r in data["momo"]]
        prices = [r[1] for r in data["momo"]]
        ax.plot(dates, prices, color=_ACCENT1, linewidth=2.5, marker="o",
                markersize=4, label="MOMO 自家", zorder=3)
        ax.fill_between(dates, prices, alpha=0.15, color=_ACCENT1)

    if data["pchome"]:
        dates2 = [r[0] for r in data["pchome"]]
        prices2 = [r[1] for r in data["pchome"]]
        ax.plot(dates2, prices2, color=_GREEN, linewidth=2, linestyle="--",
                marker="s", markersize=3, label="PChome", zorder=2)

    title = f"{'　' + sku_name if sku_name else sku}  ·  {days}日價格趨勢"
    ax.set_title(title, fontsize=12, fontweight="bold", color=_TEXT, pad=10)
    ax.set_ylabel("價格 (NT$)", color=_TEXT)
    ax.legend(loc="upper left", framealpha=0.3, facecolor=_PANEL, edgecolor=_GRID)
    ax.grid(True, alpha=0.4)
    ax.tick_params(axis="x", rotation=30, labelsize=8)
    ax.yaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(
        lambda x, _: f"${x:,.0f}"))

    # 標註最新價差
    if data["momo"] and data["pchome"]:
        m_last = data["momo"][-1][1]
        p_last = data["pchome"][-1][1]
        gap = (m_last - p_last) / m_last * 100
        color = _ACCENT1 if gap > 0 else _GREEN
        ax.annotate(f"現價差 {gap:+.1f}%", xy=(0.98, 0.05),
                    xycoords="axes fraction", ha="right",
                    fontsize=10, fontweight="bold", color=color,
                    bbox=dict(boxstyle="round,pad=0.3", facecolor=_PANEL, alpha=0.8))

    _add_watermark(ax)
    fig.tight_layout()
    return _fig_to_bytes(fig)


def revenue_trend_chart(days: int = 30, title_suffix: str = "") -> Optional[bytes]:
    """每日業績折線圖（含 MoM 同期基準線）"""
    if not _MPL_OK:
        return _unavailable()
    data = _fetch_daily_revenue(days * 2)  # 取雙倍天數供 MoM 對比
    if not data:
        return None

    curr = data[-days:] if len(data) >= days else data
    prev = data[:-days] if len(data) >= days * 2 else []

    _apply_style()
    fig, ax = plt.subplots(figsize=(12, 4))

    if curr:
        labels = [d["date"] for d in curr]
        vals   = [d["revenue"] for d in curr]
        ax.plot(labels, vals, color=_GREEN, linewidth=2.5, marker="o",
                markersize=4, label="本期", zorder=3)
        ax.fill_between(labels, vals, alpha=0.2, color=_GREEN)

        # 7天移動平均
        if len(vals) >= 7:
            ma7 = np.convolve(vals, np.ones(7)/7, mode="valid")
            ax.plot(labels[6:], ma7, color=_YELLOW, linewidth=1.5,
                    linestyle=":", label="7MA", zorder=2, alpha=0.8)

    if prev:
        labels2 = [d["date"] for d in prev[-len(curr):]]
        vals2   = [d["revenue"] for d in prev[-len(curr):]]
        ax.plot(labels2, vals2, color=_SUBTEXT, linewidth=1.5, linestyle="--",
                label="前期同比", zorder=1, alpha=0.6)

    title = f"業績趨勢  ·  近 {days} 天{title_suffix}"
    ax.set_title(title, fontsize=12, fontweight="bold", color=_TEXT, pad=10)
    ax.set_ylabel("日業績 (NT$)", color=_TEXT)
    ax.yaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(
        lambda x, _: f"${x/10000:.1f}萬"))
    ax.legend(loc="upper left", framealpha=0.3, facecolor=_PANEL, edgecolor=_GRID)
    ax.grid(True, alpha=0.4)
    ax.tick_params(axis="x", rotation=30, labelsize=7)

    # 標注最高/最低點
    if curr:
        vals = [d["revenue"] for d in curr]
        max_i, min_i = vals.index(max(vals)), vals.index(min(vals))
        ax.annotate(f"↑ ${vals[max_i]/10000:.1f}萬",
                    xy=(curr[max_i]["date"], vals[max_i]),
                    xytext=(0, 8), textcoords="offset points",
                    ha="center", fontsize=8, color=_GREEN)
        ax.annotate(f"↓ ${vals[min_i]/10000:.1f}萬",
                    xy=(curr[min_i]["date"], vals[min_i]),
                    xytext=(0, -14), textcoords="offset points",
                    ha="center", fontsize=8, color=_ACCENT1)

    _add_watermark(ax)
    fig.tight_layout()
    return _fig_to_bytes(fig)


def category_revenue_chart(days: int = 7, title_suffix: str = "") -> Optional[bytes]:
    """品類業績橫條圖"""
    if not _MPL_OK:
        return _unavailable()
    data = _fetch_category_revenue(days)
    if not data:
        return None

    _apply_style()
    n = min(len(data), 10)
    cats   = [d["category"][:10] for d in data[:n]][::-1]
    revs   = [d["revenue"] for d in data[:n]][::-1]
    max_r  = max(revs) if revs else 1

    fig, ax = plt.subplots(figsize=(10, max(4, n * 0.55)))

    colors = [_GREEN if r == max(revs) else (_ACCENT1 if r < max_r * 0.2 else _ACCENT2)
              for r in revs]
    bars = ax.barh(cats, revs, color=colors, alpha=0.85, edgecolor=_GRID, linewidth=0.5)

    for bar, val in zip(bars, revs):
        ax.text(bar.get_width() + max_r * 0.01, bar.get_y() + bar.get_height() / 2,
                f"${val/10000:.1f}萬", va="center", fontsize=9, color=_TEXT)

    ax.set_title(f"品類業績排行  ·  近 {days} 天{title_suffix}",
                 fontsize=12, fontweight="bold", color=_TEXT, pad=10)
    ax.set_xlabel("業績 (NT$)", color=_TEXT)
    ax.xaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(
        lambda x, _: f"${x/10000:.0f}萬"))
    ax.grid(True, axis="x", alpha=0.4)
    ax.set_xlim(0, max_r * 1.18)

    _add_watermark(ax)
    fig.tight_layout()
    return _fig_to_bytes(fig)


def monthly_overview_chart(months: int = 6) -> Optional[bytes]:
    """月份業績對比柱狀圖（含 MoM 成長率折線）"""
    if not _MPL_OK:
        return _unavailable()
    data = _fetch_monthly_revenue(months)
    if not data:
        return None

    _apply_style()
    labels = [d["month"] for d in data]
    vals   = [d["revenue"] for d in data]
    mom    = [0] + [(vals[i] - vals[i-1]) / vals[i-1] * 100
                    if vals[i-1] else 0 for i in range(1, len(vals))]

    fig, ax1 = plt.subplots(figsize=(10, 4.5))
    ax2 = ax1.twinx()

    bar_colors = [_GREEN if m >= 0 else _ACCENT1 for m in mom]
    bars = ax1.bar(labels, vals, color=bar_colors, alpha=0.7,
                   edgecolor=_GRID, linewidth=0.5, width=0.6)

    for bar, val in zip(bars, vals):
        ax1.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + max(vals) * 0.01,
                 f"${val/10000:.0f}萬", ha="center", fontsize=8, color=_TEXT)

    ax2.plot(labels, mom, color=_YELLOW, linewidth=2.5, marker="D",
             markersize=6, label="MoM 成長率", zorder=5)
    ax2.axhline(0, color=_SUBTEXT, linewidth=0.8, linestyle="--")
    for x, y in zip(labels, mom):
        ax2.annotate(f"{y:+.1f}%", (x, y), textcoords="offset points",
                     xytext=(0, 8), ha="center", fontsize=8,
                     color=_GREEN if y >= 0 else _ACCENT1)

    ax1.set_title(f"月度業績概覽  ·  近 {months} 個月",
                  fontsize=12, fontweight="bold", color=_TEXT, pad=10)
    ax1.set_ylabel("月業績 (NT$)", color=_TEXT)
    ax2.set_ylabel("MoM 成長率 (%)", color=_YELLOW)
    ax1.yaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(
        lambda x, _: f"${x/10000:.0f}萬"))
    ax2.tick_params(axis="y", colors=_YELLOW)
    ax1.grid(True, axis="y", alpha=0.3)
    ax1.tick_params(axis="x", rotation=20)

    _add_watermark(ax1)
    fig.tight_layout()
    return _fig_to_bytes(fig)


def price_gap_bar_chart(threats: List[Dict], title: str = "競價威脅 TOP 10") -> Optional[bytes]:
    """競價威脅價差橫條圖"""
    if not _MPL_OK or not threats:
        return _unavailable()

    _apply_style()
    n = min(len(threats), 10)
    names = [str(t.get("name", t.get("sku", "")))[:18] for t in threats[:n]][::-1]
    gaps  = [float(t.get("gap_pct", 0)) for t in threats[:n]][::-1]

    fig, ax = plt.subplots(figsize=(10, max(4, n * 0.55)))
    colors = [_ACCENT1 if g > 15 else _YELLOW if g > 5 else _GREEN for g in gaps]
    bars = ax.barh(names, gaps, color=colors, alpha=0.85, edgecolor=_GRID, linewidth=0.5)

    for bar, val in zip(bars, gaps):
        ax.text(bar.get_width() + 0.2, bar.get_y() + bar.get_height() / 2,
                f"{val:+.1f}%", va="center", fontsize=9, color=_TEXT)

    ax.axvline(0, color=_SUBTEXT, linewidth=1)
    ax.axvline(5,  color=_YELLOW,  linewidth=0.8, linestyle=":", alpha=0.6)
    ax.axvline(15, color=_ACCENT1, linewidth=0.8, linestyle=":", alpha=0.6)
    ax.set_title(title, fontsize=12, fontweight="bold", color=_TEXT, pad=10)
    ax.set_xlabel("MOMO 相對競品價差 (%) ↑我貴 / ↓我便宜", color=_TEXT)
    ax.grid(True, axis="x", alpha=0.4)

    _add_watermark(ax)
    fig.tight_layout()
    return _fig_to_bytes(fig)


def price_history_heatmap(days: int = 30) -> Optional[bytes]:
    """
    品類 × 日期 價差熱力圖（月報用）
    顯示哪個品類在哪幾天的競品威脅最嚴重
    """
    if not _MPL_OK:
        return _unavailable()
    try:
        from database.manager import get_session
        from sqlalchemy import text
        session = get_session()
        try:
            rows = session.execute(text(f"""
                SELECT p.category,
                       cp.crawled_at::date AS dt,
                       AVG((cp.price - pr.price) / NULLIF(pr.price, 0) * 100) AS gap_pct
                FROM competitor_price_history cp
                JOIN products p ON p.i_code = cp.sku
                JOIN (
                    SELECT DISTINCT ON (product_id) product_id, price
                    FROM price_records ORDER BY product_id, timestamp DESC
                ) pr ON pr.product_id = p.id
                WHERE cp.crawled_at >= NOW() - INTERVAL '{days} days'
                  AND p.category IS NOT NULL
                  AND COALESCE(cp.match_score, 0) >= 0.76
                  AND COALESCE(cp.tags, '[]'::jsonb) ? 'identity_v2'
                GROUP BY p.category, dt
                ORDER BY p.category, dt
            """)).fetchall()
        finally:
            session.close()

        if not rows:
            return None

        import pandas as pd
        df = pd.DataFrame(rows, columns=["category", "date", "gap_pct"])
        pivot = df.pivot(index="category", columns="date", values="gap_pct").fillna(0)

        _apply_style()
        fig, ax = plt.subplots(figsize=(min(16, max(8, len(pivot.columns) * 0.5)),
                                        max(4, len(pivot) * 0.6)))

        im = ax.imshow(pivot.values, cmap="RdYlGn_r", aspect="auto",
                       vmin=-20, vmax=20)
        plt.colorbar(im, ax=ax, label="價差 % (+我貴/-我便宜)", shrink=0.6)

        ax.set_xticks(range(len(pivot.columns)))
        ax.set_xticklabels([str(c)[-5:] for c in pivot.columns],
                           rotation=45, ha="right", fontsize=7)
        ax.set_yticks(range(len(pivot.index)))
        ax.set_yticklabels(pivot.index, fontsize=9)
        ax.set_title(f"品類競品價差熱力圖  ·  近 {days} 天",
                     fontsize=12, fontweight="bold", color=_TEXT, pad=10)

        _add_watermark(ax)
        fig.tight_layout()
        return _fig_to_bytes(fig)
    except Exception as e:
        logger.warning("[Chart] heatmap 生成失敗: %s", e)
        return None


def _add_watermark(ax):
    ax.text(0.99, 0.01, "EwoooC AI · momo-pro-system",
            transform=ax.transAxes, ha="right", va="bottom",
            fontsize=7, color=_SUBTEXT, alpha=0.5)


# ── 模組單例（供外部直接 import） ────────────────────────────────────────────
import matplotlib.ticker
