"""Approval token store for Autopilot."""

from __future__ import annotations

import json
import os
import secrets
import sqlite3
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Iterator


def _utc_now() -> datetime:
    return datetime.now(timezone.utc)


def default_approval_ttl_seconds(default: int = 3600) -> int:
    raw = os.getenv("G_GREMLIN_APPROVAL_TTL_SECONDS")
    if raw is None or raw == "":
        return default
    try:
        value = int(raw)
        return value if value > 0 else default
    except Exception:
        return default


@dataclass
class ApprovalToken:
    token: str
    plan_hash: str
    policy_hash: str
    run_id: str
    created_at: datetime
    expires_at: datetime
    consumed_at: datetime | None = None
    metadata: dict[str, Any] | None = None


class ApprovalTokenStore:
    def __init__(self, db_path: Path):
        self.db_path = Path(db_path)
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._init_db()

    def _init_db(self) -> None:
        with self._connect() as conn:
            conn.execute(
                """
                CREATE TABLE IF NOT EXISTS approval_tokens (
                    token TEXT PRIMARY KEY,
                    plan_hash TEXT NOT NULL,
                    policy_hash TEXT NOT NULL,
                    run_id TEXT NOT NULL,
                    created_at TEXT NOT NULL,
                    expires_at TEXT NOT NULL,
                    consumed_at TEXT,
                    metadata_json TEXT
                )
                """
            )
            conn.execute(
                "CREATE INDEX IF NOT EXISTS idx_approval_tokens_run ON approval_tokens(run_id)"
            )

    @contextmanager
    def _connect(self) -> Iterator[sqlite3.Connection]:
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        try:
            yield conn
            conn.commit()
        except Exception:
            conn.rollback()
            raise
        finally:
            conn.close()

    def create_token(
        self,
        plan_hash: str,
        policy_hash: str,
        run_id: str,
        ttl_seconds: int = 600,
        metadata: dict[str, Any] | None = None,
    ) -> ApprovalToken:
        token = secrets.token_urlsafe(24)
        now = _utc_now()
        expires_at = now + timedelta(seconds=ttl_seconds)
        with self._connect() as conn:
            conn.execute(
                """
                INSERT INTO approval_tokens (
                    token, plan_hash, policy_hash, run_id,
                    created_at, expires_at, consumed_at, metadata_json
                )
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    token,
                    plan_hash,
                    policy_hash,
                    run_id,
                    now.isoformat(),
                    expires_at.isoformat(),
                    None,
                    json.dumps(metadata) if metadata else None,
                ),
            )
        return ApprovalToken(
            token=token,
            plan_hash=plan_hash,
            policy_hash=policy_hash,
            run_id=run_id,
            created_at=now,
            expires_at=expires_at,
            consumed_at=None,
            metadata=metadata,
        )

    def validate_token(self, token: str, plan_hash: str, policy_hash: str, run_id: str) -> bool:
        record = self._get_token(token)
        if not record:
            return False
        if record.plan_hash != plan_hash or record.policy_hash != policy_hash or record.run_id != run_id:
            return False
        if record.consumed_at is not None:
            return False
        if record.expires_at <= _utc_now():
            return False
        return True

    def consume_token(
        self,
        token: str,
        plan_hash: str,
        policy_hash: str,
        run_id: str,
        metadata: dict[str, Any] | None = None,
    ) -> bool:
        now = _utc_now()
        update_args = [
            now.isoformat(),
        ]
        set_clause = "consumed_at = ?"
        if metadata is not None:
            set_clause = "consumed_at = ?, metadata_json = ?"
            update_args.append(json.dumps(metadata))

        update_args.extend([
            token,
            plan_hash,
            policy_hash,
            run_id,
            now.isoformat(),
        ])

        with self._connect() as conn:
            cursor = conn.execute(
                f"""
                UPDATE approval_tokens
                SET {set_clause}
                WHERE token = ?
                  AND consumed_at IS NULL
                  AND plan_hash = ?
                  AND policy_hash = ?
                  AND run_id = ?
                  AND expires_at > ?
                """,
                update_args,
            )
            return (cursor.rowcount or 0) == 1

    def _get_token(self, token: str) -> ApprovalToken | None:
        with self._connect() as conn:
            row = conn.execute(
                "SELECT * FROM approval_tokens WHERE token = ?",
                (token,),
            ).fetchone()
        if not row:
            return None
        metadata = json.loads(row["metadata_json"]) if row["metadata_json"] else None
        return ApprovalToken(
            token=row["token"],
            plan_hash=row["plan_hash"],
            policy_hash=row["policy_hash"],
            run_id=row["run_id"],
            created_at=datetime.fromisoformat(row["created_at"]),
            expires_at=datetime.fromisoformat(row["expires_at"]),
            consumed_at=datetime.fromisoformat(row["consumed_at"]) if row["consumed_at"] else None,
            metadata=metadata,
        )


__all__ = ["ApprovalToken", "ApprovalTokenStore", "default_approval_ttl_seconds"]
