"""Usage statistics — aggregate data from history and cost tracker."""

from __future__ import annotations

import contextlib
import json
import logging
import os
import time
from collections import Counter
from typing import TypedDict, cast

log = logging.getLogger(__name__)


class HistoryEntry(TypedDict, total=False):
    """Type definition for history entry."""

    timestamp: float
    template: str
    provider: str
    tokens: int
    time: str
    text: str
    result: str


class WeeklyBreakdown(TypedDict):
    """Type definition for weekly breakdown."""

    date: str
    count: int


class UsageStatistics:
    """Compute and display usage statistics from history data."""

    def __init__(self, history_path: str | None = None, cost_path: str | None = None) -> None:
        data_dir = os.path.expanduser("~/.muravey-china")
        self._history_path: str = history_path or os.path.join(data_dir, "history.json")
        self._cost_path: str = cost_path or os.path.join(data_dir, "cost_today.json")

    def _load_history(self) -> list[HistoryEntry]:
        try:
            with open(self._history_path, encoding="utf-8") as f:
                data = json.load(f)
                return cast(list[HistoryEntry], data)
        except (OSError, json.JSONDecodeError):
            return []

    def summary(self) -> str:
        """Generate a human-readable usage summary."""
        entries = self._load_history()
        if not entries:
            return "No usage data yet."

        total = len(entries)
        now = time.time()
        day_ago = now - 86400
        week_ago = now - 604800

        today_count = sum(1 for e in entries if e.get("timestamp", 0.0) > day_ago)
        week_count = sum(1 for e in entries if e.get("timestamp", 0.0) > week_ago)

        templates = Counter(e.get("template", "unknown") for e in entries)
        top_templates = templates.most_common(5)

        providers = Counter(e.get("provider", "unknown") for e in entries if e.get("provider"))
        top_providers = providers.most_common(3)

        token_entries = [e.get("tokens", 0) for e in entries if e.get("tokens", 0) > 0]
        avg_tokens = sum(token_entries) // len(token_entries) if token_entries else 0

        cost_today = self._load_cost_today()

        lines = [
            f"Total requests: {total}",
            f"Today: {today_count} | This week: {week_count}",
            "",
            "Top templates:",
        ]
        for name, count in top_templates:
            lines.append(f"  {name}: {count}")

        if top_providers:
            lines.append("")
            lines.append("Providers:")
            for name, count in top_providers:
                lines.append(f"  {name}: {count}")

        if avg_tokens > 0:
            lines.append(f"\nAvg tokens/request: ~{avg_tokens}")

        if cost_today:
            lines.append(f"Today's cost: ${cost_today:.4f}")

        return "\n".join(lines)

    def _load_cost_today(self) -> float:
        with contextlib.suppress(OSError, json.JSONDecodeError):
            with open(self._cost_path, encoding="utf-8") as f:
                data = json.load(f)
            today = time.strftime("%Y-%m-%d")
            if data.get("date") == today:
                cost = data.get("cost", data.get("total_cost", 0.0))
                return float(cost) if cost is not None else 0.0
        return 0.0

    def tray_summary(self) -> str:
        """Short 1-line summary for tray menu."""
        entries = self._load_history()
        now = time.time()
        today_count = sum(1 for e in entries if e.get("timestamp", 0.0) > now - 86400)
        total = len(entries)
        cost = self._load_cost_today()
        parts = [f"Today: {today_count}"]
        if cost > 0:
            parts.append(f"${cost:.4f}")
        parts.append(f"Total: {total}")
        return " | ".join(parts)

    def weekly_breakdown(self) -> list[WeeklyBreakdown]:
        """Get daily request counts for the last 7 days."""
        entries = self._load_history()
        now = time.time()
        days: dict[str, int] = {}
        for i in range(7):
            day_start = now - (i + 1) * 86400
            day_end = now - i * 86400
            date_str = time.strftime("%Y-%m-%d", time.localtime(day_end))
            count = sum(1 for e in entries if day_start < e.get("timestamp", 0.0) <= day_end)
            days[date_str] = count
        return [WeeklyBreakdown(date=d, count=c) for d, c in sorted(days.items())]

    def by_provider(self) -> dict[str, int]:
        """Get total request count per provider (all time)."""
        entries = self._load_history()
        return dict(Counter(e.get("provider", "unknown") for e in entries if e.get("provider")))

    def by_hour(self) -> dict[int, int]:
        """Get request count by hour of day (0-23) for all time."""
        entries = self._load_history()
        hours: Counter[int] = Counter()
        for e in entries:
            ts = e.get("timestamp", 0.0)
            if ts > 0:
                hour = time.localtime(ts).tm_hour
                hours[hour] += 1
        return dict(sorted(hours.items()))

    def by_template(self, limit: int = 10) -> list[tuple[str, int]]:
        """Get most used templates (all time)."""
        entries = self._load_history()
        templates = Counter(e.get("template", "unknown") for e in entries)
        return templates.most_common(limit)

    def streak(self) -> int:
        """Count consecutive days with at least one request (ending today)."""
        entries = self._load_history()
        if not entries:
            return 0
        days_with_usage: set[str] = set()
        for e in entries:
            ts = e.get("timestamp", 0.0)
            if ts > 0:
                days_with_usage.add(time.strftime("%Y-%m-%d", time.localtime(ts)))
        today = time.strftime("%Y-%m-%d")
        if today not in days_with_usage:
            return 0
        count = 0
        check = time.time()
        for _ in range(365):
            day_str = time.strftime("%Y-%m-%d", time.localtime(check))
            if day_str in days_with_usage:
                count += 1
                check -= 86400
            else:
                break
        return count

    def detailed_summary(self) -> str:
        """Comprehensive multi-section statistics report."""
        entries = self._load_history()
        if not entries:
            return "No usage data yet."

        now = time.time()
        total = len(entries)
        today_count = sum(1 for e in entries if e.get("timestamp", 0.0) > now - 86400)
        week_count = sum(1 for e in entries if e.get("timestamp", 0.0) > now - 604800)
        month_count = sum(1 for e in entries if e.get("timestamp", 0.0) > now - 2592000)

        lines = [
            "=== MuraveyChina Statistics ===",
            "",
            f"Total: {total} requests",
            f"Today: {today_count} | Week: {week_count} | Month: {month_count}",
        ]

        s = self.streak()
        if s > 0:
            lines.append(f"Current streak: {s} day{'s' if s != 1 else ''}")

        weekly = self.weekly_breakdown()
        if weekly:
            lines.append("")
            lines.append("Last 7 days:")
            for day in weekly:
                bar = "\u2588" * min(day["count"], 30)
                lines.append(f"  {day['date']}: {bar} {day['count']}")

        top = self.by_template(5)
        if top:
            lines.append("")
            lines.append("Top templates:")
            for name, count in top:
                lines.append(f"  {name}: {count}")

        providers = self.by_provider()
        if providers:
            lines.append("")
            lines.append("By provider:")
            for name, count in sorted(providers.items(), key=lambda x: -x[1]):
                lines.append(f"  {name}: {count}")

        hours = self.by_hour()
        if hours:
            peak_hour = max(hours.keys(), key=lambda h: hours[h])
            lines.append("")
            lines.append(f"Peak hour: {peak_hour}:00 ({hours[peak_hour]} requests)")

        cost = self._load_cost_today()
        if cost > 0:
            lines.append(f"\nToday's cost: ${cost:.4f}")

        return "\n".join(lines)
