"""
BIBIVIDI — Stats Tracker
Records token savings per execution and aggregates metrics.
"""
from __future__ import annotations

import logging
from datetime import UTC, datetime, timedelta

import aiosqlite

from bvd.config import settings

logger = logging.getLogger(__name__)


class StatsTracker:

    async def record(
        self,
        script_id: str | None,
        tokens_before: int,
        tokens_after: int,
        latency_ms: float = 0.0,
        request_id: str | None = None,
        model_name: str | None = None,
        model_price_per_token_eur: float | None = None,
    ) -> int | None:
        """Record an execution and return the new execution row id, or None."""
        if not script_id:
            return None

        from bvd.license import hash_license_key, load_license_key
        raw_key = load_license_key()
        license_key_hash = hash_license_key(raw_key) if raw_key else None

        async with aiosqlite.connect(settings.db_path) as db:  # type: ignore[arg-type]
            await db.execute(
                """
                INSERT INTO executions
                    (script_id, tokens_before, tokens_after, latency_ms, executed_at,
                     request_id, model_name, model_price_per_token_eur, license_key_hash)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    script_id,
                    tokens_before,
                    tokens_after,
                    latency_ms,
                    datetime.now(UTC).isoformat(),
                    request_id,
                    model_name,
                    model_price_per_token_eur,
                    license_key_hash,
                ),
            )
            async with db.execute("SELECT last_insert_rowid()") as cur:
                row = await cur.fetchone()
            execution_id: int | None = row[0] if row else None
            await db.commit()
        if tokens_before > 0:
            saving_pct = (1 - tokens_after / tokens_before) * 100
            logger.info("Saved %.0f%% tokens via script %s", saving_pct, script_id)
        return execution_id

    async def total_saved(self) -> int:
        """Return total tokens saved across all executions."""
        try:
            async with aiosqlite.connect(settings.db_path) as db:  # type: ignore[arg-type]
                async with db.execute(
                    "SELECT COALESCE(SUM(tokens_before - tokens_after), 0) FROM executions"
                ) as cur:
                    row = await cur.fetchone()
            return int(row[0]) if row else 0
        except Exception:
            return 0

    async def summary(self) -> dict:
        """Return a summary dict for the dashboard."""
        try:
            async with aiosqlite.connect(settings.db_path) as db:  # type: ignore[arg-type]
                async with db.execute(
                    """
                    SELECT
                        COUNT(*)                                             AS calls,
                        COALESCE(SUM(tokens_before), 0)                     AS tokens_in,
                        COALESCE(SUM(tokens_after), 0)                      AS tokens_out,
                        COALESCE(SUM(tokens_before - tokens_after), 0)      AS saved,
                        COALESCE(AVG(latency_ms), 0)                        AS avg_latency_ms
                    FROM executions
                    """
                ) as cur:
                    row = await cur.fetchone()
            if not row:
                return {}
            calls, tokens_in, tokens_out, saved, avg_latency = row
            ratio = (1 - tokens_out / tokens_in) if tokens_in else 0
            return {
                "total_calls": calls,
                "tokens_in": tokens_in,
                "tokens_out": tokens_out,
                "tokens_saved": saved,
                "saving_ratio": round(ratio, 3),
                "avg_latency_ms": round(avg_latency, 1),
            }
        except Exception:
            return {}


    async def weekly_summary(self) -> dict:
        """Return token savings for the past 7 days plus top skill."""
        since = (datetime.now(UTC) - timedelta(days=7)).isoformat()
        try:
            async with aiosqlite.connect(settings.db_path) as db:  # type: ignore[arg-type]
                async with db.execute(
                    """
                    SELECT
                        COUNT(*)                                              AS calls,
                        COALESCE(SUM(tokens_before - tokens_after), 0)       AS saved,
                        COALESCE(SUM(tokens_before), 0)                      AS tokens_in
                    FROM executions
                    WHERE executed_at >= ?
                    """,
                    (since,),
                ) as cur:
                    row = await cur.fetchone()

                async with db.execute(
                    """
                    SELECT script_id, COUNT(*) AS uses
                    FROM executions
                    WHERE executed_at >= ? AND script_id IS NOT NULL
                    GROUP BY script_id
                    ORDER BY uses DESC
                    LIMIT 1
                    """,
                    (since,),
                ) as cur:
                    top_row = await cur.fetchone()

            calls, saved, tokens_in = (row[0], row[1], row[2]) if row else (0, 0, 0)
            top_skill = top_row[0] if top_row else None
            return {
                "calls": int(calls),
                "tokens_saved": int(saved),
                "tokens_in": int(tokens_in),
                "top_skill": top_skill,
                "estimated_usd_saved": round(saved * 0.000001, 4),
                "co2_grams": round(saved * 0.003, 1),
            }
        except Exception:
            return {
                "calls": 0, "tokens_saved": 0, "tokens_in": 0,
                "top_skill": None, "estimated_usd_saved": 0.0, "co2_grams": 0.0,
            }


    async def quarterly_summary(self, year: int, quarter: int) -> dict:
        """Return token savings and billing data for a quarter (Q1–Q4).

        Returns a dict with: calls, tokens_saved, tokens_in, savings_eur,
        bibividi_share_eur, co2_grams, and top_skills (list of top 3 skill ids).
        """
        from bvd.billing import DEFAULT_PRICE_EUR, TIERS
        from bvd.config import settings

        q_months = {1: (1, 3), 2: (4, 6), 3: (7, 9), 4: (10, 12)}
        start_month, end_month = q_months.get(quarter, (1, 3))

        import calendar
        _, last_day = calendar.monthrange(year, end_month)
        start = f"{year}-{start_month:02d}-01T00:00:00+00:00"
        end = f"{year}-{end_month:02d}-{last_day:02d}T23:59:59+00:00"

        try:
            async with aiosqlite.connect(settings.db_path) as db:  # type: ignore[arg-type]
                async with db.execute(
                    """
                    SELECT
                        COUNT(*)                                          AS calls,
                        COALESCE(SUM(tokens_before - tokens_after), 0)   AS saved,
                        COALESCE(SUM(tokens_before), 0)                  AS tokens_in,
                        COALESCE(SUM(
                            (tokens_before - tokens_after) *
                            COALESCE(model_price_per_token_eur, ?)
                        ), 0)                                            AS savings_eur
                    FROM executions
                    WHERE executed_at >= ? AND executed_at <= ?
                      AND tokens_before > tokens_after
                    """,
                    (DEFAULT_PRICE_EUR, start, end),
                ) as cur:
                    row = await cur.fetchone()

                # Top 3 skills by use count
                async with db.execute(
                    """
                    SELECT script_id, COUNT(*) AS uses
                    FROM executions
                    WHERE executed_at >= ? AND executed_at <= ?
                      AND script_id IS NOT NULL
                    GROUP BY script_id
                    ORDER BY uses DESC
                    LIMIT 3
                    """,
                    (start, end),
                ) as cur:
                    top_rows = await cur.fetchall()

            calls = int(row[0]) if row else 0
            saved = int(row[1]) if row else 0
            tokens_in = int(row[2]) if row else 0
            savings_eur = float(row[3]) if row else 0.0
            top_skills = [{"skill_id": r[0], "uses": int(r[1])} for r in top_rows]

            # Apply tier pricing to compute BIBIVIDI share
            tier = getattr(settings, "billing_tier", "pro")
            rate, cap = TIERS.get(tier, TIERS["pro"])
            raw_share = savings_eur * rate
            share = min(raw_share, cap) if cap != float("inf") else raw_share

            return {
                "calls": calls,
                "tokens_saved": saved,
                "tokens_in": tokens_in,
                "savings_eur": round(savings_eur, 4),
                "bibividi_share_eur": round(share, 4),
                "co2_grams": round(saved * 0.003, 1),
                "top_skills": top_skills,
            }
        except Exception:
            return {
                "calls": 0, "tokens_saved": 0, "tokens_in": 0,
                "savings_eur": 0.0, "bibividi_share_eur": 0.0,
                "co2_grams": 0.0, "top_skills": [],
            }

    async def monthly_summary(self) -> dict:
        """Return token savings for the past 30 days."""
        since = (datetime.now(UTC) - timedelta(days=30)).isoformat()
        try:
            async with aiosqlite.connect(settings.db_path) as db:  # type: ignore[arg-type]
                async with db.execute(
                    """
                    SELECT
                        COUNT(*)                                              AS calls,
                        COALESCE(SUM(tokens_before - tokens_after), 0)       AS saved,
                        COALESCE(SUM(tokens_before), 0)                      AS tokens_in
                    FROM executions
                    WHERE executed_at >= ?
                    """,
                    (since,),
                ) as cur:
                    row = await cur.fetchone()
            calls, saved, tokens_in = (row[0], row[1], row[2]) if row else (0, 0, 0)
            ratio = (saved / tokens_in) if tokens_in else 0.0
            return {
                "calls": int(calls),
                "tokens_saved": int(saved),
                "tokens_in": int(tokens_in),
                "saving_ratio": round(ratio, 3),
                "estimated_usd_saved": round(saved * 0.000001, 4),
                "co2_grams": round(saved * 0.003, 1),
            }
        except Exception:
            return {
                "calls": 0, "tokens_saved": 0, "tokens_in": 0,
                "saving_ratio": 0.0, "estimated_usd_saved": 0.0, "co2_grams": 0.0,
            }

    async def last_used_model(self) -> str | None:
        """Return the model name from the most recent execution, or None."""
        try:
            async with aiosqlite.connect(settings.db_path) as db:  # type: ignore[arg-type]
                async with db.execute(
                    "SELECT model_name FROM executions "
                    "WHERE model_name IS NOT NULL ORDER BY executed_at DESC LIMIT 1"
                ) as cur:
                    row = await cur.fetchone()
            return row[0] if row else None
        except Exception:
            return None

    async def flag_bad(self, request_id: str) -> str | None:
        """Mark an execution as bad feedback. Returns skill_id if found, None otherwise."""
        try:
            async with aiosqlite.connect(settings.db_path) as db:  # type: ignore[arg-type]
                async with db.execute(
                    "SELECT id, script_id FROM executions WHERE request_id = ?",
                    (request_id,),
                ) as cur:
                    row = await cur.fetchone()
                if not row:
                    return None
                exec_id, skill_id = row[0], row[1]
                await db.execute(
                    "UPDATE executions SET feedback = 'bad' WHERE id = ?", (exec_id,)
                )
                await db.commit()
            logger.info("Flagged execution %s (skill=%s) as bad", request_id, skill_id)
            return skill_id
        except Exception as exc:
            logger.error("flag_bad failed: %s", exc)
            return None

    async def list_flagged(self) -> list[dict]:
        """Return all executions flagged as bad, most recent first."""
        try:
            async with aiosqlite.connect(settings.db_path) as db:  # type: ignore[arg-type]
                db.row_factory = aiosqlite.Row
                async with db.execute(
                    """
                    SELECT script_id, request_id, executed_at, tokens_before, tokens_after
                    FROM executions
                    WHERE feedback = 'bad'
                    ORDER BY executed_at DESC
                    LIMIT 100
                    """
                ) as cur:
                    rows = await cur.fetchall()
            return [dict(r) for r in rows]
        except Exception:
            return []

    async def record_chain(
        self,
        chain_script_id: str,
        step_results: list[dict],
        tokens_before: int,
        tokens_after: int,
        latency_ms: float = 0.0,
    ) -> None:
        """Record a chain execution and per-step metrics in chain_steps."""
        if not chain_script_id:
            return
        async with aiosqlite.connect(settings.db_path) as db:  # type: ignore[arg-type]
            await db.execute(
                """
                INSERT INTO executions
                    (script_id, tokens_before, tokens_after, latency_ms, executed_at)
                VALUES (?, ?, ?, ?, ?)
                """,
                (
                    chain_script_id, tokens_before, tokens_after, latency_ms,
                    datetime.now(UTC).isoformat(),
                ),
            )
            async with db.execute("SELECT last_insert_rowid()") as cur:
                row = await cur.fetchone()
            execution_id = row[0] if row else None

            if execution_id is not None:
                for step_num, step in enumerate(step_results):
                    await db.execute(
                        """
                        INSERT INTO chain_steps
                            (execution_id, step_num, skill_id, tokens_in, tokens_out, latency_ms)
                        VALUES (?, ?, ?, ?, ?, ?)
                        """,
                        (
                            execution_id,
                            step_num,
                            step["skill_id"],
                            step["tokens_in"],
                            step["tokens_out"],
                            step.get("latency_ms"),
                        ),
                    )
            await db.commit()


stats_tracker = StatsTracker()


async def record_execution(
    script_id: str,
    tokens_before: int,
    tokens_after: int,
    latency_ms: float = 0.0,
    request_id: str | None = None,
    model_name: str | None = None,
    model_price_per_token_eur: float | None = None,
    license_key: str | None = None,
) -> int | None:
    """Standalone helper — record an execution with an explicit license_key.

    Unlike StatsTracker.record(), this function accepts the key directly rather
    than loading it from config, making it easier to test in isolation.
    """
    from bvd.license import hash_license_key

    license_key_hash = hash_license_key(license_key) if license_key else None

    async with aiosqlite.connect(settings.db_path) as db:  # type: ignore[arg-type]
        await db.execute(
            """
            INSERT INTO executions
                (script_id, tokens_before, tokens_after, latency_ms, executed_at,
                 request_id, model_name, model_price_per_token_eur, license_key_hash)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                script_id,
                tokens_before,
                tokens_after,
                latency_ms,
                datetime.now(UTC).isoformat(),
                request_id,
                model_name,
                model_price_per_token_eur,
                license_key_hash,
            ),
        )
        async with db.execute("SELECT last_insert_rowid()") as cur:
            row = await cur.fetchone()
        execution_id: int | None = row[0] if row else None
        await db.commit()
    return execution_id
