"""LLMHosts billing event store — append-only SQLite for Stripe metered billing (Phase 5)."""

from __future__ import annotations

import logging
from datetime import datetime
from typing import TYPE_CHECKING

import aiosqlite

from llmhosts.billing.models import BillingEvent

if TYPE_CHECKING:
    from pathlib import Path

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# SQL
# ---------------------------------------------------------------------------

_CREATE_TABLE = """
CREATE TABLE IF NOT EXISTS billing_events (
    request_id           TEXT NOT NULL,
    timestamp            TEXT NOT NULL,
    api_key_id           TEXT NOT NULL DEFAULT '',
    device_id            TEXT,
    model                TEXT NOT NULL,
    prompt_tokens        INTEGER NOT NULL DEFAULT 0,
    completion_tokens    INTEGER NOT NULL DEFAULT 0,
    total_tokens         INTEGER NOT NULL DEFAULT 0,
    price_cents          INTEGER,
    source               TEXT NOT NULL DEFAULT 'marketplace'
);
"""

_CREATE_INDEXES = [
    "CREATE INDEX IF NOT EXISTS idx_billing_timestamp ON billing_events (timestamp);",
    "CREATE INDEX IF NOT EXISTS idx_billing_api_key ON billing_events (api_key_id);",
    "CREATE INDEX IF NOT EXISTS idx_billing_device ON billing_events (device_id);",
]

_INSERT = """
INSERT INTO billing_events (
    request_id, timestamp, api_key_id, device_id, model,
    prompt_tokens, completion_tokens, total_tokens, price_cents, source
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""


def _iso(dt: datetime) -> str:
    return dt.isoformat()


class BillingStore:
    """Append-only SQLite store for billing events.

    Used by the dispatcher when a remote (marketplace) request completes.
    A future Stripe metered-billing job can read events via get_last_n()
    or get_since() and report usage.
    """

    def __init__(self, db_path: Path) -> None:
        self._db_path = db_path
        self._db: aiosqlite.Connection | None = None

    async def initialize(self) -> None:
        """Create billing_events table and indexes. Enable WAL."""
        self._db_path.parent.mkdir(parents=True, exist_ok=True)
        self._db = await aiosqlite.connect(str(self._db_path))
        await self._db.execute("PRAGMA journal_mode=WAL;")
        await self._db.execute("PRAGMA synchronous=NORMAL;")
        await self._db.execute(_CREATE_TABLE)
        for idx_sql in _CREATE_INDEXES:
            await self._db.execute(idx_sql)
        await self._db.commit()
        logger.info("BillingStore initialised (db=%s)", self._db_path)

    async def close(self) -> None:
        """Close database connection."""
        if self._db is not None:
            await self._db.close()
            self._db = None
            logger.debug("BillingStore closed")

    def _ensure_db(self) -> aiosqlite.Connection:
        if self._db is None:
            raise RuntimeError("BillingStore not initialised -- call initialize() first")
        return self._db

    async def append(self, event: BillingEvent) -> None:
        """Append a single billing event. Best-effort; logs and swallows errors."""
        db = self._ensure_db()
        try:
            await db.execute(
                _INSERT,
                (
                    event.request_id,
                    _iso(event.timestamp),
                    event.api_key_id,
                    event.device_id,
                    event.model,
                    event.prompt_tokens,
                    event.completion_tokens,
                    event.total_tokens,
                    event.price_cents,
                    event.source,
                ),
            )
            await db.commit()
            logger.debug("Billing event appended request_id=%s model=%s", event.request_id, event.model)
        except Exception:
            logger.warning("Failed to append billing event request_id=%s", event.request_id, exc_info=True)

    async def get_last_n(self, n: int) -> list[BillingEvent]:
        """Return the last N events (newest first)."""
        db = self._ensure_db()
        sql = """
        SELECT request_id, timestamp, api_key_id, device_id, model,
               prompt_tokens, completion_tokens, total_tokens, price_cents, source
        FROM billing_events
        ORDER BY timestamp DESC
        LIMIT ?
        """
        events: list[BillingEvent] = []
        async with db.execute(sql, (n,)) as cursor:
            async for row in cursor:
                events.append(
                    BillingEvent(
                        request_id=row[0],
                        timestamp=datetime.fromisoformat(row[1]),
                        api_key_id=row[2] or "",
                        device_id=row[3],
                        model=row[4],
                        prompt_tokens=row[5],
                        completion_tokens=row[6],
                        total_tokens=row[7],
                        price_cents=row[8],
                        source=row[9] or "marketplace",
                    )
                )
        return events

    async def get_since(self, since: datetime) -> list[BillingEvent]:
        """Return all events with timestamp >= since (ascending order)."""
        db = self._ensure_db()
        sql = """
        SELECT request_id, timestamp, api_key_id, device_id, model,
               prompt_tokens, completion_tokens, total_tokens, price_cents, source
        FROM billing_events
        WHERE timestamp >= ?
        ORDER BY timestamp ASC
        """
        events: list[BillingEvent] = []
        async with db.execute(sql, (_iso(since),)) as cursor:
            async for row in cursor:
                events.append(
                    BillingEvent(
                        request_id=row[0],
                        timestamp=datetime.fromisoformat(row[1]),
                        api_key_id=row[2] or "",
                        device_id=row[3],
                        model=row[4],
                        prompt_tokens=row[5],
                        completion_tokens=row[6],
                        total_tokens=row[7],
                        price_cents=row[8],
                        source=row[9] or "marketplace",
                    )
                )
        return events
