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)


@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": "月"},
        }

    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)

        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

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

        return {
            "grain": grain,
            "period": {"start": start.isoformat(), "end": end.isoformat(), "label": label},
            "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),
            },
            "sections": {"stores": stores, "products": products, "traffic": traffic},
            "charts": {"trend": trend},
        }
