"""
SQLite backend — default for local development and single-node deployments.
"""

import hashlib
import secrets
import sqlite3
from datetime import datetime

from autopil.models import AuditEvent, Decision

from .base import AuditLogBase, KeyStoreBase, TenantStoreBase, PolicyStoreBase

KEY_PREFIX = "apl_"


def _hash(key: str) -> str:
    return hashlib.sha256(key.encode()).hexdigest()


def _has_column(conn: sqlite3.Connection, table: str, column: str) -> bool:
    rows = conn.execute(f"PRAGMA table_info({table})").fetchall()
    return any(r[1] == column for r in rows)


class SQLiteAuditLog(AuditLogBase):

    def __init__(self, db_path: str):
        self.db_path = db_path
        self._init_db()

    def _init_db(self):
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS audit_events (
                    event_id      TEXT PRIMARY KEY,
                    session_id    TEXT NOT NULL,
                    agent_role    TEXT NOT NULL,
                    user_id       TEXT NOT NULL,
                    source_id     TEXT NOT NULL,
                    query         TEXT NOT NULL,
                    decision      TEXT NOT NULL,
                    policy_name   TEXT NOT NULL,
                    reason        TEXT NOT NULL,
                    context_hash  TEXT,
                    timestamp     TEXT NOT NULL
                )
            """)
            # Migration: add tenant_id if upgrading from pre-multi-tenancy schema
            if not _has_column(conn, "audit_events", "tenant_id"):
                conn.execute(
                    "ALTER TABLE audit_events ADD COLUMN tenant_id TEXT NOT NULL DEFAULT 'default'"
                )
            conn.commit()

    def record(self, event: AuditEvent, tenant_id: str = "default") -> None:
        with sqlite3.connect(self.db_path) as conn:
            conn.execute(
                """INSERT INTO audit_events
                   VALUES (?,?,?,?,?,?,?,?,?,?,?,?)""",
                (
                    event.event_id,
                    event.session_id,
                    event.agent_role,
                    event.user_id,
                    event.source_id,
                    event.query,
                    event.decision.value,
                    event.policy_name,
                    event.reason,
                    event.context_hash,
                    event.timestamp.isoformat(),
                    tenant_id,
                ),
            )
            conn.commit()

    def get_session_events(self, session_id: str, tenant_id: str = "default") -> list[AuditEvent]:
        with sqlite3.connect(self.db_path) as conn:
            rows = conn.execute(
                "SELECT * FROM audit_events WHERE session_id = ? AND tenant_id = ? ORDER BY timestamp",
                (session_id, tenant_id),
            ).fetchall()
        return [self._row_to_event(r) for r in rows]

    def get_all_events(self, tenant_id: str = "default") -> list[AuditEvent]:
        with sqlite3.connect(self.db_path) as conn:
            rows = conn.execute(
                "SELECT * FROM audit_events WHERE tenant_id = ? ORDER BY timestamp",
                (tenant_id,),
            ).fetchall()
        return [self._row_to_event(r) for r in rows]

    def get_session_owner(self, session_id: str, tenant_id: str = "default") -> str | None:
        with sqlite3.connect(self.db_path) as conn:
            row = conn.execute(
                """SELECT agent_role FROM audit_events
                   WHERE session_id = ? AND tenant_id = ?
                   ORDER BY timestamp LIMIT 1""",
                (session_id, tenant_id),
            ).fetchone()
        return row[0] if row else None

    def _row_to_event(self, row) -> AuditEvent:
        return AuditEvent(
            event_id=row[0],
            session_id=row[1],
            agent_role=row[2],
            user_id=row[3],
            source_id=row[4],
            query=row[5],
            decision=Decision(row[6]),
            policy_name=row[7],
            reason=row[8],
            timestamp=datetime.fromisoformat(row[10]),
            context_hash=row[9],
        )


class SQLiteKeyStore(KeyStoreBase):

    def __init__(self, db_path: str):
        self.db_path = db_path
        self._init_db()

    def _init_db(self):
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS api_keys (
                    key_id        TEXT PRIMARY KEY,
                    key_hash      TEXT NOT NULL UNIQUE,
                    name          TEXT NOT NULL,
                    created_at    TEXT NOT NULL,
                    last_used     TEXT,
                    is_active     INTEGER NOT NULL DEFAULT 1,
                    tenant_id     TEXT NOT NULL DEFAULT 'default',
                    is_superadmin INTEGER NOT NULL DEFAULT 0
                )
            """)
            # Migrations for pre-multi-tenancy schema
            if not _has_column(conn, "api_keys", "tenant_id"):
                conn.execute(
                    "ALTER TABLE api_keys ADD COLUMN tenant_id TEXT NOT NULL DEFAULT 'default'"
                )
            if not _has_column(conn, "api_keys", "is_superadmin"):
                conn.execute(
                    "ALTER TABLE api_keys ADD COLUMN is_superadmin INTEGER NOT NULL DEFAULT 0"
                )

    def create_key(self, name: str, tenant_id: str = "default", is_superadmin: bool = False) -> tuple[str, str]:
        key_id    = secrets.token_hex(8)
        plaintext = KEY_PREFIX + secrets.token_hex(24)
        with sqlite3.connect(self.db_path) as conn:
            conn.execute(
                """INSERT INTO api_keys
                   (key_id, key_hash, name, created_at, tenant_id, is_superadmin)
                   VALUES (?, ?, ?, ?, ?, ?)""",
                (key_id, _hash(plaintext), name, datetime.utcnow().isoformat(),
                 tenant_id, int(is_superadmin)),
            )
        return key_id, plaintext

    def validate(self, plaintext: str) -> dict | None:
        if not plaintext.startswith(KEY_PREFIX):
            return None
        with sqlite3.connect(self.db_path) as conn:
            row = conn.execute(
                """SELECT key_id, tenant_id, is_superadmin FROM api_keys
                   WHERE key_hash = ? AND is_active = 1""",
                (_hash(plaintext),),
            ).fetchone()
            if row:
                conn.execute(
                    "UPDATE api_keys SET last_used = ? WHERE key_id = ?",
                    (datetime.utcnow().isoformat(), row[0]),
                )
                return {"key_id": row[0], "tenant_id": row[1], "is_superadmin": bool(row[2])}
        return None

    def list_keys(self, tenant_id: str = "default") -> list[dict]:
        with sqlite3.connect(self.db_path) as conn:
            rows = conn.execute(
                """SELECT key_id, name, created_at, last_used, is_active, tenant_id, is_superadmin
                   FROM api_keys WHERE tenant_id = ? ORDER BY created_at""",
                (tenant_id,),
            ).fetchall()
        return [
            {"key_id": r[0], "name": r[1], "created_at": r[2], "last_used": r[3],
             "is_active": bool(r[4]), "tenant_id": r[5], "is_superadmin": bool(r[6])}
            for r in rows
        ]

    def revoke(self, key_id: str, tenant_id: str = "default") -> bool:
        with sqlite3.connect(self.db_path) as conn:
            cur = conn.execute(
                "UPDATE api_keys SET is_active = 0 WHERE key_id = ? AND tenant_id = ? AND is_active = 1",
                (key_id, tenant_id),
            )
        return cur.rowcount > 0

    def count(self, tenant_id: str | None = None) -> int:
        with sqlite3.connect(self.db_path) as conn:
            if tenant_id:
                return conn.execute(
                    "SELECT COUNT(*) FROM api_keys WHERE tenant_id = ?", (tenant_id,)
                ).fetchone()[0]
            return conn.execute("SELECT COUNT(*) FROM api_keys").fetchone()[0]


class SQLiteTenantStore(TenantStoreBase):

    def __init__(self, db_path: str):
        self.db_path = db_path
        self._init_db()

    def _init_db(self):
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS tenants (
                    tenant_id  TEXT PRIMARY KEY,
                    name       TEXT NOT NULL UNIQUE,
                    created_at TEXT NOT NULL,
                    is_active  INTEGER NOT NULL DEFAULT 1
                )
            """)

    def create_tenant(self, name: str) -> str:
        tenant_id = "ten_" + secrets.token_hex(8)
        with sqlite3.connect(self.db_path) as conn:
            conn.execute(
                "INSERT INTO tenants (tenant_id, name, created_at) VALUES (?, ?, ?)",
                (tenant_id, name, datetime.utcnow().isoformat()),
            )
        return tenant_id

    def get_tenant(self, tenant_id: str) -> dict | None:
        with sqlite3.connect(self.db_path) as conn:
            row = conn.execute(
                "SELECT tenant_id, name, created_at, is_active FROM tenants WHERE tenant_id = ?",
                (tenant_id,),
            ).fetchone()
        if not row:
            return None
        return {"tenant_id": row[0], "name": row[1], "created_at": row[2], "is_active": bool(row[3])}

    def list_tenants(self) -> list[dict]:
        with sqlite3.connect(self.db_path) as conn:
            rows = conn.execute(
                "SELECT tenant_id, name, created_at, is_active FROM tenants ORDER BY created_at"
            ).fetchall()
        return [
            {"tenant_id": r[0], "name": r[1], "created_at": r[2], "is_active": bool(r[3])}
            for r in rows
        ]

    def deactivate_tenant(self, tenant_id: str) -> bool:
        with sqlite3.connect(self.db_path) as conn:
            cur = conn.execute(
                "UPDATE tenants SET is_active = 0 WHERE tenant_id = ? AND is_active = 1",
                (tenant_id,),
            )
        return cur.rowcount > 0

    def count(self) -> int:
        with sqlite3.connect(self.db_path) as conn:
            return conn.execute("SELECT COUNT(*) FROM tenants").fetchone()[0]


import json


class SQLitePolicyStore(PolicyStoreBase):

    def __init__(self, db_path: str):
        self.db_path = db_path
        self._init_db()

    def _init_db(self):
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS policies (
                    policy_id       TEXT PRIMARY KEY,
                    name            TEXT NOT NULL,
                    agent_role      TEXT NOT NULL,
                    allowed_sources TEXT NOT NULL DEFAULT '[]',
                    denied_sources  TEXT NOT NULL DEFAULT '[]',
                    max_sensitivity TEXT NOT NULL DEFAULT 'high',
                    tenant_id       TEXT NOT NULL,
                    created_at      TEXT NOT NULL,
                    updated_at      TEXT NOT NULL,
                    version         INTEGER NOT NULL DEFAULT 1,
                    changed_by      TEXT,
                    is_active       INTEGER NOT NULL DEFAULT 1,
                    UNIQUE(name, tenant_id)
                )
            """)
            conn.execute("""
                CREATE TABLE IF NOT EXISTS policy_versions (
                    version_id      TEXT PRIMARY KEY,
                    policy_id       TEXT NOT NULL,
                    name            TEXT NOT NULL,
                    agent_role      TEXT NOT NULL,
                    allowed_sources TEXT NOT NULL DEFAULT '[]',
                    denied_sources  TEXT NOT NULL DEFAULT '[]',
                    max_sensitivity TEXT NOT NULL,
                    version         INTEGER NOT NULL,
                    tenant_id       TEXT NOT NULL,
                    changed_by      TEXT,
                    changed_at      TEXT NOT NULL
                )
            """)
            conn.commit()

    def upsert(self, policy: dict, tenant_id: str, changed_by: str | None = None) -> str:
        name = policy["name"]
        now  = datetime.utcnow().isoformat()
        allowed = json.dumps(policy.get("allowed_sources", []))
        denied  = json.dumps(policy.get("denied_sources", []))
        sensitivity = policy.get("max_sensitivity", "high")
        agent_role  = policy["agent_role"]

        with sqlite3.connect(self.db_path) as conn:
            existing = conn.execute(
                "SELECT policy_id, version FROM policies WHERE name = ? AND tenant_id = ? AND is_active = 1",
                (name, tenant_id),
            ).fetchone()

            if existing:
                policy_id, version = existing
                new_version = version + 1
                conn.execute(
                    """UPDATE policies SET
                       agent_role=?, allowed_sources=?, denied_sources=?,
                       max_sensitivity=?, updated_at=?, version=?, changed_by=?
                       WHERE policy_id=?""",
                    (agent_role, allowed, denied, sensitivity, now, new_version, changed_by, policy_id),
                )
                # record the new version in history
                conn.execute(
                    """INSERT INTO policy_versions
                       (version_id, policy_id, name, agent_role, allowed_sources,
                        denied_sources, max_sensitivity, version, tenant_id, changed_by, changed_at)
                       VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
                    (secrets.token_hex(8), policy_id, name, agent_role, allowed, denied,
                     sensitivity, new_version, tenant_id, changed_by, now),
                )
            else:
                policy_id = "pol_" + secrets.token_hex(8)
                conn.execute(
                    """INSERT INTO policies
                       (policy_id, name, agent_role, allowed_sources, denied_sources,
                        max_sensitivity, tenant_id, created_at, updated_at, version, changed_by)
                       VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)""",
                    (policy_id, name, agent_role, allowed, denied, sensitivity, tenant_id, now, now, changed_by),
                )
                # record version 1
                conn.execute(
                    """INSERT INTO policy_versions
                       (version_id, policy_id, name, agent_role, allowed_sources,
                        denied_sources, max_sensitivity, version, tenant_id, changed_by, changed_at)
                       VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?)""",
                    (secrets.token_hex(8), policy_id, name, agent_role, allowed, denied, sensitivity, tenant_id, changed_by, now),
                )
            conn.commit()
        return policy_id

    def get(self, name: str, tenant_id: str) -> dict | None:
        with sqlite3.connect(self.db_path) as conn:
            row = conn.execute(
                """SELECT policy_id, name, agent_role, allowed_sources, denied_sources,
                          max_sensitivity, version, created_at, updated_at, changed_by
                   FROM policies WHERE name = ? AND tenant_id = ? AND is_active = 1""",
                (name, tenant_id),
            ).fetchone()
        return self._row_to_dict(row) if row else None

    def list_active(self, tenant_id: str) -> list[dict]:
        with sqlite3.connect(self.db_path) as conn:
            rows = conn.execute(
                """SELECT policy_id, name, agent_role, allowed_sources, denied_sources,
                          max_sensitivity, version, created_at, updated_at, changed_by
                   FROM policies WHERE tenant_id = ? AND is_active = 1 ORDER BY name""",
                (tenant_id,),
            ).fetchall()
        return [self._row_to_dict(r) for r in rows]

    def delete(self, name: str, tenant_id: str) -> bool:
        with sqlite3.connect(self.db_path) as conn:
            cur = conn.execute(
                "UPDATE policies SET is_active = 0 WHERE name = ? AND tenant_id = ? AND is_active = 1",
                (name, tenant_id),
            )
            conn.commit()
        return cur.rowcount > 0

    def get_history(self, name: str, tenant_id: str) -> list[dict]:
        with sqlite3.connect(self.db_path) as conn:
            # Get the policy_id first
            row = conn.execute(
                "SELECT policy_id FROM policies WHERE name = ? AND tenant_id = ?",
                (name, tenant_id),
            ).fetchone()
            if not row:
                return []
            policy_id = row[0]
            rows = conn.execute(
                """SELECT version_id, version, agent_role, allowed_sources, denied_sources,
                          max_sensitivity, changed_by, changed_at
                   FROM policy_versions WHERE policy_id = ? AND tenant_id = ?
                   ORDER BY version DESC""",
                (policy_id, tenant_id),
            ).fetchall()
        return [
            {
                "version_id": r[0],
                "version": r[1],
                "agent_role": r[2],
                "allowed_sources": json.loads(r[3]),
                "denied_sources": json.loads(r[4]),
                "max_sensitivity": r[5],
                "changed_by": r[6],
                "changed_at": r[7],
            }
            for r in rows
        ]

    def count(self, tenant_id: str) -> int:
        with sqlite3.connect(self.db_path) as conn:
            return conn.execute(
                "SELECT COUNT(*) FROM policies WHERE tenant_id = ? AND is_active = 1",
                (tenant_id,),
            ).fetchone()[0]

    def _row_to_dict(self, row) -> dict:
        return {
            "policy_id": row[0],
            "name": row[1],
            "agent_role": row[2],
            "allowed_sources": json.loads(row[3]),
            "denied_sources": json.loads(row[4]),
            "max_sensitivity": row[5],
            "version": row[6],
            "created_at": row[7],
            "updated_at": row[8],
            "changed_by": row[9],
        }
