"""Persistência e enforcement — atribuições Agentics × função (Spec 027 UI-C)."""

from __future__ import annotations

import json
import sqlite3
from datetime import datetime, timezone
from typing import Any

RELATIONS = ("ui", "focus", "approve")
CAP_IDS = ("use_ui", "trigger_runs", "approve_runbooks", "configure_models")

LOCKED_AGENT_BINDINGS: frozenset[tuple[str, str, str]] = frozenset({
    ("A7", "agentic_operator", "approve"),
    ("A7", "super_admin", "approve"),
})

AGENT_IDS = ("A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7")


def _spec():
    from app.rbac_matrix import AGENTIC_GOVERNANCE, AGENT_ROLE_MAP, ROLE_COLUMNS

    return AGENTIC_GOVERNANCE, AGENT_ROLE_MAP, ROLE_COLUMNS


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _default_agent_enabled(agent_id: str, role_id: str, relation: str) -> bool:
    _, AGENT_ROLE_MAP, _ = _spec()
    from app.rbac_matrix import AGENTIC_GOVERNANCE

    mapping = AGENT_ROLE_MAP.get(agent_id, {})
    if relation == "approve":
        return role_id in mapping.get("approvers", [])
    if relation == "focus":
        return role_id in mapping.get("operators", [])
    if relation == "ui":
        return role_id in AGENTIC_GOVERNANCE["use_ui"]
    return False


def _default_cap_enabled(cap_id: str, role_id: str) -> bool:
    AGENTIC_GOVERNANCE, _, _ = _spec()
    roles = AGENTIC_GOVERNANCE.get(cap_id)
    if isinstance(roles, list):
        return role_id in roles
    return False


def _is_locked(agent_id: str, role_id: str, relation: str) -> bool:
    return (agent_id, role_id, relation) in LOCKED_AGENT_BINDINGS


def init_schema(conn: sqlite3.Connection) -> None:
    conn.executescript(
        """
        CREATE TABLE IF NOT EXISTS agent_role_bindings (
            agent_id TEXT NOT NULL,
            role_id TEXT NOT NULL,
            relation TEXT NOT NULL,
            enabled INTEGER NOT NULL DEFAULT 1,
            locked INTEGER NOT NULL DEFAULT 0,
            updated_at TEXT NOT NULL,
            updated_by TEXT,
            PRIMARY KEY (agent_id, role_id, relation)
        );
        CREATE TABLE IF NOT EXISTS agent_governance_caps (
            cap_id TEXT NOT NULL,
            role_id TEXT NOT NULL,
            enabled INTEGER NOT NULL DEFAULT 1,
            locked INTEGER NOT NULL DEFAULT 0,
            updated_at TEXT NOT NULL,
            updated_by TEXT,
            PRIMARY KEY (cap_id, role_id)
        );
        CREATE TABLE IF NOT EXISTS rbac_agent_audit (
            id INTEGER PRIMARY KEY,
            ts TEXT NOT NULL,
            username TEXT NOT NULL,
            action TEXT NOT NULL,
            payload_json TEXT NOT NULL
        );
        CREATE INDEX IF NOT EXISTS idx_rbac_agent_audit_ts ON rbac_agent_audit(ts DESC);
        """
    )
    _seed_defaults(conn)


def _seed_defaults(conn: sqlite3.Connection) -> None:
    _, _, ROLE_COLUMNS = _spec()
    now = _now()
    if conn.execute("SELECT COUNT(*) c FROM agent_role_bindings").fetchone()["c"] == 0:
        rows = []
        for agent_id in AGENT_IDS:
            for role_id in ROLE_COLUMNS:
                for relation in RELATIONS:
                    enabled = 1 if _default_agent_enabled(agent_id, role_id, relation) else 0
                    locked = 1 if _is_locked(agent_id, role_id, relation) else 0
                    rows.append((agent_id, role_id, relation, enabled, locked, now, "system"))
        conn.executemany(
            """INSERT INTO agent_role_bindings
               (agent_id, role_id, relation, enabled, locked, updated_at, updated_by)
               VALUES (?, ?, ?, ?, ?, ?, ?)""",
            rows,
        )
    if conn.execute("SELECT COUNT(*) c FROM agent_governance_caps").fetchone()["c"] == 0:
        cap_rows = []
        for cap_id in CAP_IDS:
            for role_id in ROLE_COLUMNS:
                enabled = 1 if _default_cap_enabled(cap_id, role_id) else 0
                cap_rows.append((cap_id, role_id, enabled, 0, now, "system"))
        conn.executemany(
            """INSERT INTO agent_governance_caps
               (cap_id, role_id, enabled, locked, updated_at, updated_by)
               VALUES (?, ?, ?, ?, ?, ?)""",
            cap_rows,
        )


def audit_log(
    conn: sqlite3.Connection,
    *,
    username: str,
    action: str,
    payload: dict[str, Any],
) -> None:
    conn.execute(
        "INSERT INTO rbac_agent_audit (ts, username, action, payload_json) VALUES (?, ?, ?, ?)",
        (_now(), username, action, json.dumps(payload)),
    )


def list_audit(conn: sqlite3.Connection, *, limit: int = 50) -> list[dict[str, Any]]:
    rows = conn.execute(
        "SELECT id, ts, username, action, payload_json FROM rbac_agent_audit ORDER BY id DESC LIMIT ?",
        (limit,),
    ).fetchall()
    out = []
    for r in rows:
        item = dict(r)
        try:
            item["payload"] = json.loads(item.pop("payload_json") or "{}")
        except json.JSONDecodeError:
            item["payload"] = {}
        out.append(item)
    return out


def _row_enabled(row: sqlite3.Row | None, default: bool = False) -> bool:
    if row is None:
        return default
    return bool(row["enabled"])


def agent_relations(conn: sqlite3.Connection, agent_id: str, role_id: str) -> list[str]:
    """Relações activas função ↔ agente."""
    if role_id == "super_admin":
        return list(RELATIONS)
    rels: list[str] = []
    for relation in RELATIONS:
        row = conn.execute(
            """SELECT enabled FROM agent_role_bindings
               WHERE agent_id=? AND role_id=? AND relation=?""",
            (agent_id, role_id, relation),
        ).fetchone()
        if _row_enabled(row, _default_agent_enabled(agent_id, role_id, relation)):
            rels.append(relation)
    return rels


def cap_enabled(conn: sqlite3.Connection, cap_id: str, role_id: str) -> bool:
    if role_id == "super_admin":
        return True
    row = conn.execute(
        "SELECT enabled FROM agent_governance_caps WHERE cap_id=? AND role_id=?",
        (cap_id, role_id),
    ).fetchone()
    return _row_enabled(row, _default_cap_enabled(cap_id, role_id))


def can_use_agentics_ui(conn: sqlite3.Connection, role_id: str) -> bool:
    if role_id == "super_admin":
        return True
    return cap_enabled(conn, "use_ui", role_id)


def can_chat_agent(conn: sqlite3.Connection, role_id: str, agent_id: str) -> bool:
    if role_id == "super_admin":
        return True
    rels = agent_relations(conn, agent_id, role_id)
    return bool(set(rels) & {"ui", "focus", "approve"})


def can_approve_agent(conn: sqlite3.Connection, role_id: str, agent_id: str) -> bool:
    if role_id == "super_admin":
        return True
    return "approve" in agent_relations(conn, agent_id, role_id)


def can_trigger_runs(conn: sqlite3.Connection, role_id: str) -> bool:
    if role_id == "super_admin":
        return True
    return cap_enabled(conn, "trigger_runs", role_id)


def can_approve_runbooks(conn: sqlite3.Connection, role_id: str) -> bool:
    if role_id == "super_admin":
        return True
    return cap_enabled(conn, "approve_runbooks", role_id)


def load_bindings_matrix(conn: sqlite3.Connection) -> dict[str, Any]:
    """Estado completo para UI / export."""
    agent_rows = conn.execute(
        "SELECT agent_id, role_id, relation, enabled, locked FROM agent_role_bindings"
    ).fetchall()
    cap_rows = conn.execute(
        "SELECT cap_id, role_id, enabled, locked FROM agent_governance_caps"
    ).fetchall()

    agents: dict[str, dict[str, dict[str, dict[str, Any]]]] = {}
    for r in agent_rows:
        agents.setdefault(r["agent_id"], {}).setdefault(r["role_id"], {})[r["relation"]] = {
            "enabled": bool(r["enabled"]),
            "locked": bool(r["locked"]),
        }

    caps: dict[str, dict[str, dict[str, Any]]] = {}
    for r in cap_rows:
        caps.setdefault(r["cap_id"], {})[r["role_id"]] = {
            "enabled": bool(r["enabled"]),
            "locked": bool(r["locked"]),
        }

    return {"agents": agents, "caps": caps}


def role_agentic_caps(conn: sqlite3.Connection, role_id: str) -> list[dict[str, str]]:
    from app.rbac_matrix import AGENTIC_GOVERNANCE

    caps = []
    for cap_id in CAP_IDS:
        if cap_enabled(conn, cap_id, role_id):
            caps.append({
                "id": cap_id,
                "label": AGENTIC_GOVERNANCE["labels"].get(cap_id, cap_id),
            })
    return caps


def set_agent_binding(
    conn: sqlite3.Connection,
    *,
    agent_id: str,
    role_id: str,
    relation: str,
    enabled: bool,
    username: str,
) -> dict[str, Any]:
    _, _, ROLE_COLUMNS = _spec()
    if agent_id not in AGENT_IDS:
        raise ValueError(f"agente inválido: {agent_id}")
    if role_id not in ROLE_COLUMNS:
        raise ValueError(f"função inválida: {role_id}")
    if relation not in RELATIONS:
        raise ValueError(f"relação inválida: {relation}")

    row = conn.execute(
        """SELECT enabled, locked FROM agent_role_bindings
           WHERE agent_id=? AND role_id=? AND relation=?""",
        (agent_id, role_id, relation),
    ).fetchone()
    locked = bool(row["locked"]) if row else _is_locked(agent_id, role_id, relation)
    if locked and not enabled:
        raise ValueError("binding bloqueado pela Spec 027 (obrigatório)")

    now = _now()
    if row:
        conn.execute(
            """UPDATE agent_role_bindings SET enabled=?, updated_at=?, updated_by=?
               WHERE agent_id=? AND role_id=? AND relation=?""",
            (1 if enabled else 0, now, username, agent_id, role_id, relation),
        )
    else:
        conn.execute(
            """INSERT INTO agent_role_bindings
               (agent_id, role_id, relation, enabled, locked, updated_at, updated_by)
               VALUES (?, ?, ?, ?, ?, ?, ?)""",
            (agent_id, role_id, relation, 1 if enabled else 0, 1 if locked else 0, now, username),
        )

    audit_log(
        conn,
        username=username,
        action="agent_binding.update",
        payload={
            "agent_id": agent_id,
            "role_id": role_id,
            "relation": relation,
            "enabled": enabled,
        },
    )
    return {
        "agent_id": agent_id,
        "role_id": role_id,
        "relation": relation,
        "enabled": enabled,
        "locked": locked,
    }


def set_governance_cap(
    conn: sqlite3.Connection,
    *,
    cap_id: str,
    role_id: str,
    enabled: bool,
    username: str,
) -> dict[str, Any]:
    _, _, ROLE_COLUMNS = _spec()
    if cap_id not in CAP_IDS:
        raise ValueError(f"capacidade inválida: {cap_id}")
    if role_id not in ROLE_COLUMNS:
        raise ValueError(f"função inválida: {role_id}")

    row = conn.execute(
        "SELECT enabled, locked FROM agent_governance_caps WHERE cap_id=? AND role_id=?",
        (cap_id, role_id),
    ).fetchone()
    locked = bool(row["locked"]) if row else False
    if locked and not enabled:
        raise ValueError("capacidade bloqueada")

    now = _now()
    if row:
        conn.execute(
            """UPDATE agent_governance_caps SET enabled=?, updated_at=?, updated_by=?
               WHERE cap_id=? AND role_id=?""",
            (1 if enabled else 0, now, username, cap_id, role_id),
        )
    else:
        conn.execute(
            """INSERT INTO agent_governance_caps
               (cap_id, role_id, enabled, locked, updated_at, updated_by)
               VALUES (?, ?, ?, 0, ?, ?)""",
            (cap_id, role_id, 1 if enabled else 0, now, username),
        )

    audit_log(
        conn,
        username=username,
        action="agent_cap.update",
        payload={"cap_id": cap_id, "role_id": role_id, "enabled": enabled},
    )
    return {"cap_id": cap_id, "role_id": role_id, "enabled": enabled, "locked": locked}
