from __future__ import annotations

import calendar
from dataclasses import dataclass
from datetime import date, datetime, timedelta
from typing import Any, Protocol


class DashboardRepositoryProtocol(Protocol):
    def latest_date(self) -> str | None: ...
    def available_dates(self) -> list[str]: ...
    def store_summary(self, start_date: date, end_date: date) -> list[dict[str, Any]]: ...
    def trend(self, start_date: date, end_date: date) -> list[dict[str, Any]]: ...
    def product_rank(self, start_date: date, end_date: date, limit: int = 20) -> list[dict[str, Any]]: ...
    def traffic_rank(self, start_date: date, end_date: date, limit: int = 20) -> list[dict[str, Any]]: ...


def parse_iso_date(value: str | None, fallback: str | None = None) -> date:
    raw = value or fallback
    if not raw:
        return date.today()
    return datetime.strptime(raw, "%Y-%m-%d").date()


def resolve_period(grain: str, selected: date) -> tuple[date, date, str]:
    if grain == "day":
        return selected, selected, selected.isoformat()
    if grain == "week":
        start = selected - timedelta(days=selected.weekday())
        end = start + timedelta(days=6)
        return start, end, f"{start.isoformat()} 至 {end.isoformat()}"
    if grain == "month":
        start = selected.replace(day=1)
        end = selected.replace(day=calendar.monthrange(selected.year, selected.month)[1])
        return start, end, selected.strftime("%Y-%m")
    raise ValueError("grain must be one of: day, week, month")


def number(value: Any) -> float:
    if value is None or value == "":
        return 0.0
    try:
        return float(value)
    except Exception:
        return 0.0


def round2(value: float) -> float:
    return round(float(value or 0), 2)


def optional_rows(repo: Any, method: str, *args: Any) -> list[dict[str, Any]]:
    fn = getattr(repo, method, None)
    if not callable(fn):
        return []
    return fn(*args)


def enrich_money_rate(row: dict[str, Any]) -> None:
    for key in [
        "promotion_spend_amount",
        "promoted_transaction_amount",
        "net_payment_amount_for_rate",
        "promotion_click_count",
        "promotion_impression_count",
        "promoted_transaction_count",
    ]:
        row[key] = round2(number(row.get(key)))
    for key in ["promotion_roi", "promotion_ctr", "promotion_cpc", "promotion_spend_rate", "promotion_transaction_share"]:
        row[key] = round2(number(row.get(key)))


@dataclass
class DashboardService:
    repository: DashboardRepositoryProtocol

    def meta(self) -> dict[str, Any]:
        return {
            "latest_date": self.repository.latest_date(),
            "available_dates": self.repository.available_dates(),
            "grains": ["day", "week", "month"],
            "grain_labels": {"day": "日", "week": "周", "month": "月"},
            "promotion_note": "推广花费/成交默认采用阿里妈妈万相台营销场景报表日粒度归因口径。",
        }

    def dashboard(self, grain: str, selected_date: str | None) -> dict[str, Any]:
        latest = self.repository.latest_date()
        selected = parse_iso_date(selected_date, latest)
        start, end, label = resolve_period(grain, selected)
        stores = self.repository.store_summary(start, end)
        trend = self.repository.trend(start, end)
        products = self.repository.product_rank(start, end, 20)
        traffic = self.repository.traffic_rank(start, end, 20)
        promotion_stores = optional_rows(self.repository, "promotion_store_summary", start, end)
        promotion_trend = optional_rows(self.repository, "promotion_trend", start, end)
        promotion_scenes = optional_rows(self.repository, "promotion_scene_rank", start, end, 20)
        promotion_plans = optional_rows(self.repository, "promotion_plan_rank", start, end, 20)

        total_sales = sum(number(r.get("net_payment_amount") or r.get("payment_amount")) for r in stores)
        total_uv = sum(number(r.get("visitor_count")) for r in stores)
        total_pv = sum(number(r.get("page_view_count")) for r in stores)
        total_buyers = sum(number(r.get("payment_buyer_count")) for r in stores)
        conversion = (total_buyers / total_uv) if total_uv else 0
        avg_order = (total_sales / total_buyers) if total_buyers else 0

        promotion_by_store = {r.get("store_name"): r for r in promotion_stores}
        for row in promotion_stores + promotion_scenes + promotion_plans + promotion_trend:
            enrich_money_rate(row)

        total_promo_spend = sum(number(r.get("promotion_spend_amount")) for r in promotion_stores)
        total_promo_transaction = sum(number(r.get("promoted_transaction_amount")) for r in promotion_stores)
        total_promo_clicks = sum(number(r.get("promotion_click_count")) for r in promotion_stores)
        total_promo_impressions = sum(number(r.get("promotion_impression_count")) for r in promotion_stores)
        total_promo_orders = sum(number(r.get("promoted_transaction_count")) for r in promotion_stores)
        promotion_roi = (total_promo_transaction / total_promo_spend) if total_promo_spend else 0
        promotion_spend_rate = (total_promo_spend / total_sales) if total_sales else 0
        promotion_transaction_share = (total_promo_transaction / total_sales) if total_sales else 0
        promotion_ctr = (total_promo_clicks / total_promo_impressions) if total_promo_impressions else 0
        promotion_cpc = (total_promo_spend / total_promo_clicks) if total_promo_clicks else 0

        for row in stores:
            uv = number(row.get("visitor_count"))
            net = number(row.get("net_payment_amount") or row.get("payment_amount"))
            buyers = number(row.get("payment_buyer_count"))
            row["net_payment_amount"] = round2(net)
            row["visitor_count"] = round2(uv)
            row["page_view_count"] = round2(number(row.get("page_view_count")))
            row["payment_buyer_count"] = round2(buyers)
            row["payment_conversion_rate"] = round2(buyers / uv) if uv else 0
            row["customer_unit_price"] = round2(net / buyers) if buyers else 0
            row["sales_share"] = round2(net / total_sales) if total_sales else 0
            promo = promotion_by_store.get(row.get("store_name"), {})
            row["promotion_spend_amount"] = round2(number(promo.get("promotion_spend_amount")))
            row["promoted_transaction_amount"] = round2(number(promo.get("promoted_transaction_amount")))
            row["promotion_roi"] = round2(number(promo.get("promotion_roi")))
            row["promotion_spend_rate"] = round2(row["promotion_spend_amount"] / net) if net else 0
            row["promotion_transaction_share"] = round2(row["promoted_transaction_amount"] / net) if net else 0

        return {
            "grain": grain,
            "period": {"start": start.isoformat(), "end": end.isoformat(), "label": label},
            "data_notes": {
                "promotion_source": "阿里妈妈/万相台：营销场景报表用于总花费和场景汇总；计划报表只用于计划 TOP，避免重复加总。",
                "promotion_roi": "ROI 为平台后台当前成交归因口径：推广成交金额 / 推广花费。",
            },
            "kpis": {
                "net_payment_amount": round2(total_sales),
                "visitor_count": round2(total_uv),
                "page_view_count": round2(total_pv),
                "payment_buyer_count": round2(total_buyers),
                "payment_conversion_rate": round2(conversion),
                "customer_unit_price": round2(avg_order),
                "active_store_count": len(stores),
                "product_count": len(products),
                "promotion_spend_amount": round2(total_promo_spend),
                "promoted_transaction_amount": round2(total_promo_transaction),
                "promotion_roi": round2(promotion_roi),
                "promotion_spend_rate": round2(promotion_spend_rate),
                "promotion_transaction_share": round2(promotion_transaction_share),
                "promotion_click_count": round2(total_promo_clicks),
                "promotion_impression_count": round2(total_promo_impressions),
                "promotion_ctr": round2(promotion_ctr),
                "promotion_cpc": round2(promotion_cpc),
                "promoted_transaction_count": round2(total_promo_orders),
            },
            "sections": {
                "stores": stores,
                "products": products,
                "traffic": traffic,
                "promotion_stores": promotion_stores,
                "promotion_scenes": promotion_scenes,
                "promotion_plans": promotion_plans,
            },
            "charts": {"trend": trend, "promotion_trend": promotion_trend},
        }
