"""SQLite persistence for repository mesh control artifacts."""

from __future__ import annotations

import json
import sqlite3
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Mapping, Sequence

from .models import GeneratedFile, ServiceOrder, as_plain_data, utc_now
from .repository_mesh import MeshReport, MeshRiskLevel
from .repository_mesh_reconciliation import ReconciliationPlan, ReconciliationStatus
from .repository_mesh_runtime import RuntimeCycle, SchedulerSpec


SCHEMA = """
CREATE TABLE IF NOT EXISTS repository_mesh_reports (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    report_id TEXT UNIQUE NOT NULL,
    generated_at TEXT NOT NULL,
    fetch_enabled INTEGER NOT NULL,
    targets_count INTEGER NOT NULL,
    observations_count INTEGER NOT NULL,
    ok_count INTEGER NOT NULL,
    attention_count INTEGER NOT NULL,
    blocked_count INTEGER NOT NULL,
    dirty_repositories INTEGER NOT NULL,
    credential_errors INTEGER NOT NULL,
    destructive_blocks INTEGER NOT NULL,
    plugin_auth_attempt TEXT NOT NULL,
    payload_json TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS repository_mesh_targets (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    report_id TEXT NOT NULL,
    declared_name TEXT NOT NULL,
    expected_local_name TEXT NOT NULL,
    gitea_repo TEXT NOT NULL,
    risk TEXT NOT NULL,
    aligned_hash TEXT,
    hash_count INTEGER NOT NULL,
    dirty_count INTEGER NOT NULL,
    missing_count INTEGER NOT NULL,
    remote_mismatch_count INTEGER NOT NULL,
    nominal_mismatch_count INTEGER NOT NULL,
    credential_error_count INTEGER NOT NULL,
    destructive_block_count INTEGER NOT NULL,
    summary TEXT NOT NULL,
    payload_json TEXT NOT NULL,
    updated_at TEXT NOT NULL,
    UNIQUE(report_id, declared_name)
);

CREATE TABLE IF NOT EXISTS repository_mesh_observations (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    report_id TEXT NOT NULL,
    declared_name TEXT NOT NULL,
    environment_id TEXT NOT NULL,
    presence TEXT NOT NULL,
    matched_path TEXT,
    branch TEXT,
    head TEXT,
    remote_origin TEXT,
    cleanliness TEXT,
    ahead INTEGER,
    behind INTEGER,
    fetch_error_kind TEXT,
    nomenclature_note TEXT,
    payload_json TEXT NOT NULL,
    updated_at TEXT NOT NULL,
    UNIQUE(report_id, declared_name, environment_id)
);

CREATE TABLE IF NOT EXISTS repository_mesh_actions (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    report_id TEXT NOT NULL,
    action_id TEXT UNIQUE NOT NULL,
    declared_name TEXT NOT NULL,
    environment_id TEXT NOT NULL,
    kind TEXT NOT NULL,
    risk TEXT NOT NULL,
    automatic INTEGER NOT NULL,
    destructive INTEGER NOT NULL,
    reason TEXT NOT NULL,
    payload_json TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS repository_mesh_reconciliation_plans (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    plan_id TEXT UNIQUE NOT NULL,
    report_id TEXT NOT NULL,
    generated_at TEXT NOT NULL,
    blocker_count INTEGER NOT NULL,
    auto_sync_ready_count INTEGER NOT NULL,
    rename_ready_count INTEGER NOT NULL,
    credential_blocked_count INTEGER NOT NULL,
    payload_json TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS repository_mesh_receipts (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    plan_id TEXT NOT NULL,
    receipt_id TEXT UNIQUE NOT NULL,
    target_name TEXT NOT NULL,
    expected_local_name TEXT NOT NULL,
    gitea_repo TEXT NOT NULL,
    status TEXT NOT NULL,
    risk TEXT NOT NULL,
    safe_to_auto_sync INTEGER NOT NULL,
    selected_head TEXT,
    precedence TEXT NOT NULL,
    pending_count INTEGER NOT NULL,
    blocker_count INTEGER NOT NULL,
    payload_json TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS repository_mesh_runtime_cycles (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    cycle_id TEXT UNIQUE NOT NULL,
    report_id TEXT NOT NULL,
    plan_id TEXT NOT NULL,
    generated_at TEXT NOT NULL,
    lock_status TEXT NOT NULL,
    allowed_count INTEGER NOT NULL,
    blocked_count INTEGER NOT NULL,
    skipped_count INTEGER NOT NULL,
    payload_json TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS repository_mesh_schedulers (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    scheduler_id TEXT UNIQUE NOT NULL,
    kind TEXT NOT NULL,
    name TEXT NOT NULL,
    interval_minutes INTEGER NOT NULL,
    command TEXT NOT NULL,
    working_directory TEXT NOT NULL,
    log_path TEXT NOT NULL,
    payload_json TEXT NOT NULL,
    updated_at TEXT NOT NULL
);
"""


@dataclass(frozen=True, slots=True)
class MeshSemanticCounts:
    reports: int
    targets: int
    observations: int
    actions: int
    plans: int
    receipts: int
    cycles: int
    schedulers: int

    def to_dict(self) -> dict[str, int]:
        return as_plain_data(self)


def connect(path: Path) -> sqlite3.Connection:
    path.parent.mkdir(parents=True, exist_ok=True)
    conn = sqlite3.connect(path)
    conn.execute("PRAGMA journal_mode=WAL")
    conn.execute("PRAGMA synchronous=NORMAL")
    conn.executescript(SCHEMA)
    return conn


def payload(value: object) -> str:
    return json.dumps(as_plain_data(value), ensure_ascii=False, sort_keys=True)


def upsert_report(conn: sqlite3.Connection, report: MeshReport) -> None:
    now = utc_now()
    conn.execute(
        """
        INSERT INTO repository_mesh_reports (
            report_id, generated_at, fetch_enabled, targets_count, observations_count,
            ok_count, attention_count, blocked_count, dirty_repositories,
            credential_errors, destructive_blocks, plugin_auth_attempt,
            payload_json, updated_at
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ON CONFLICT(report_id) DO UPDATE SET
            generated_at=excluded.generated_at,
            fetch_enabled=excluded.fetch_enabled,
            targets_count=excluded.targets_count,
            observations_count=excluded.observations_count,
            ok_count=excluded.ok_count,
            attention_count=excluded.attention_count,
            blocked_count=excluded.blocked_count,
            dirty_repositories=excluded.dirty_repositories,
            credential_errors=excluded.credential_errors,
            destructive_blocks=excluded.destructive_blocks,
            plugin_auth_attempt=excluded.plugin_auth_attempt,
            payload_json=excluded.payload_json,
            updated_at=excluded.updated_at
        """,
        (
            report.report_id,
            report.generated_at,
            1 if report.fetch_enabled else 0,
            len(report.targets),
            report.total_observations,
            report.ok_count,
            report.attention_count,
            report.blocked_count,
            report.dirty_repositories,
            report.credential_errors,
            report.destructive_blocks,
            report.plugin_auth_attempt,
            payload(report),
            now,
        ),
    )
    for summary in report.summaries:
        conn.execute(
            """
            INSERT INTO repository_mesh_targets (
                report_id, declared_name, expected_local_name, gitea_repo, risk,
                aligned_hash, hash_count, dirty_count, missing_count,
                remote_mismatch_count, nominal_mismatch_count, credential_error_count,
                destructive_block_count, summary, payload_json, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(report_id, declared_name) DO UPDATE SET
                expected_local_name=excluded.expected_local_name,
                gitea_repo=excluded.gitea_repo,
                risk=excluded.risk,
                aligned_hash=excluded.aligned_hash,
                hash_count=excluded.hash_count,
                dirty_count=excluded.dirty_count,
                missing_count=excluded.missing_count,
                remote_mismatch_count=excluded.remote_mismatch_count,
                nominal_mismatch_count=excluded.nominal_mismatch_count,
                credential_error_count=excluded.credential_error_count,
                destructive_block_count=excluded.destructive_block_count,
                summary=excluded.summary,
                payload_json=excluded.payload_json,
                updated_at=excluded.updated_at
            """,
            (
                report.report_id,
                summary.target.declared_name,
                summary.target.expected_local_name,
                summary.target.gitea_repo,
                summary.risk.value,
                summary.aligned_hash,
                len(summary.hash_set),
                summary.dirty_count,
                summary.missing_count,
                summary.remote_mismatch_count,
                summary.nominal_mismatch_count,
                summary.credential_error_count,
                summary.destructive_block_count,
                summary.summary,
                payload(summary),
                now,
            ),
        )
        for obs in summary.observations:
            state = obs.git_state
            conn.execute(
                """
                INSERT INTO repository_mesh_observations (
                    report_id, declared_name, environment_id, presence, matched_path,
                    branch, head, remote_origin, cleanliness, ahead, behind,
                    fetch_error_kind, nomenclature_note, payload_json, updated_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                ON CONFLICT(report_id, declared_name, environment_id) DO UPDATE SET
                    presence=excluded.presence,
                    matched_path=excluded.matched_path,
                    branch=excluded.branch,
                    head=excluded.head,
                    remote_origin=excluded.remote_origin,
                    cleanliness=excluded.cleanliness,
                    ahead=excluded.ahead,
                    behind=excluded.behind,
                    fetch_error_kind=excluded.fetch_error_kind,
                    nomenclature_note=excluded.nomenclature_note,
                    payload_json=excluded.payload_json,
                    updated_at=excluded.updated_at
                """,
                (
                    report.report_id,
                    summary.target.declared_name,
                    obs.environment_id,
                    obs.presence.value,
                    obs.matched_path,
                    state.branch if state else None,
                    state.head if state else None,
                    state.remote_origin if state else None,
                    state.cleanliness.value if state else None,
                    state.ahead_behind.ahead if state else None,
                    state.ahead_behind.behind if state else None,
                    state.fetch_error_kind.value if state else None,
                    obs.nomenclature_note,
                    payload(obs),
                    now,
                ),
            )
        for action in summary.actions:
            conn.execute(
                """
                INSERT INTO repository_mesh_actions (
                    report_id, action_id, declared_name, environment_id, kind,
                    risk, automatic, destructive, reason, payload_json, updated_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                ON CONFLICT(action_id) DO UPDATE SET
                    report_id=excluded.report_id,
                    declared_name=excluded.declared_name,
                    environment_id=excluded.environment_id,
                    kind=excluded.kind,
                    risk=excluded.risk,
                    automatic=excluded.automatic,
                    destructive=excluded.destructive,
                    reason=excluded.reason,
                    payload_json=excluded.payload_json,
                    updated_at=excluded.updated_at
                """,
                (
                    report.report_id,
                    action.action_id,
                    action.target_name,
                    action.environment_id,
                    action.kind.value,
                    action.risk.value,
                    1 if action.can_execute_automatically else 0,
                    1 if action.destructive else 0,
                    action.reason,
                    payload(action),
                    now,
                ),
            )


def upsert_plan(conn: sqlite3.Connection, plan: ReconciliationPlan) -> None:
    now = utc_now()
    conn.execute(
        """
        INSERT INTO repository_mesh_reconciliation_plans (
            plan_id, report_id, generated_at, blocker_count, auto_sync_ready_count,
            rename_ready_count, credential_blocked_count, payload_json, updated_at
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
        ON CONFLICT(plan_id) DO UPDATE SET
            report_id=excluded.report_id,
            generated_at=excluded.generated_at,
            blocker_count=excluded.blocker_count,
            auto_sync_ready_count=excluded.auto_sync_ready_count,
            rename_ready_count=excluded.rename_ready_count,
            credential_blocked_count=excluded.credential_blocked_count,
            payload_json=excluded.payload_json,
            updated_at=excluded.updated_at
        """,
        (
            plan.plan_id,
            plan.report_id,
            plan.generated_at,
            plan.blocker_count,
            plan.auto_sync_ready_count,
            plan.rename_ready_count,
            plan.credential_blocked_count,
            payload(plan),
            now,
        ),
    )
    for receipt in plan.receipts:
        conn.execute(
            """
            INSERT INTO repository_mesh_receipts (
                plan_id, receipt_id, target_name, expected_local_name, gitea_repo,
                status, risk, safe_to_auto_sync, selected_head, precedence,
                pending_count, blocker_count, payload_json, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(receipt_id) DO UPDATE SET
                plan_id=excluded.plan_id,
                target_name=excluded.target_name,
                expected_local_name=excluded.expected_local_name,
                gitea_repo=excluded.gitea_repo,
                status=excluded.status,
                risk=excluded.risk,
                safe_to_auto_sync=excluded.safe_to_auto_sync,
                selected_head=excluded.selected_head,
                precedence=excluded.precedence,
                pending_count=excluded.pending_count,
                blocker_count=excluded.blocker_count,
                payload_json=excluded.payload_json,
                updated_at=excluded.updated_at
            """,
            (
                plan.plan_id,
                receipt.receipt_id,
                receipt.target_name,
                receipt.expected_local_name,
                receipt.gitea_repo,
                receipt.status.value,
                receipt.risk.value,
                1 if receipt.safe_to_auto_sync else 0,
                receipt.latest_state.selected_head,
                receipt.latest_state.precedence.value,
                len(receipt.pending_items),
                len(receipt.blockers),
                payload(receipt),
                now,
            ),
        )


def upsert_runtime(conn: sqlite3.Connection, cycle: RuntimeCycle, schedulers: Sequence[SchedulerSpec]) -> None:
    now = utc_now()
    conn.execute(
        """
        INSERT INTO repository_mesh_runtime_cycles (
            cycle_id, report_id, plan_id, generated_at, lock_status,
            allowed_count, blocked_count, skipped_count, payload_json, updated_at
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ON CONFLICT(cycle_id) DO UPDATE SET
            report_id=excluded.report_id,
            plan_id=excluded.plan_id,
            generated_at=excluded.generated_at,
            lock_status=excluded.lock_status,
            allowed_count=excluded.allowed_count,
            blocked_count=excluded.blocked_count,
            skipped_count=excluded.skipped_count,
            payload_json=excluded.payload_json,
            updated_at=excluded.updated_at
        """,
        (
            cycle.cycle_id,
            cycle.report_id,
            cycle.plan_id,
            cycle.generated_at,
            cycle.lock.status.value,
            cycle.allowed_count,
            cycle.blocked_count,
            cycle.skipped_count,
            payload(cycle),
            now,
        ),
    )
    for spec in schedulers:
        conn.execute(
            """
            INSERT INTO repository_mesh_schedulers (
                scheduler_id, kind, name, interval_minutes, command,
                working_directory, log_path, payload_json, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(scheduler_id) DO UPDATE SET
                kind=excluded.kind,
                name=excluded.name,
                interval_minutes=excluded.interval_minutes,
                command=excluded.command,
                working_directory=excluded.working_directory,
                log_path=excluded.log_path,
                payload_json=excluded.payload_json,
                updated_at=excluded.updated_at
            """,
            (
                spec.scheduler_id,
                spec.kind.value,
                spec.name,
                spec.interval_minutes,
                spec.command,
                spec.working_directory,
                spec.log_path,
                payload(spec),
                now,
            ),
        )


def write_repository_mesh_semantic_state(
    sqlite_path: Path,
    *,
    report: MeshReport,
    plan: ReconciliationPlan,
    cycle: RuntimeCycle,
    schedulers: Sequence[SchedulerSpec],
) -> MeshSemanticCounts:
    with connect(sqlite_path) as conn:
        upsert_report(conn, report)
        upsert_plan(conn, plan)
        upsert_runtime(conn, cycle, schedulers)
        conn.commit()
    return table_counts(sqlite_path)


def table_counts(sqlite_path: Path) -> MeshSemanticCounts:
    if not sqlite_path.exists():
        return MeshSemanticCounts(0, 0, 0, 0, 0, 0, 0, 0)
    with sqlite3.connect(sqlite_path) as conn:
        def count(table: str) -> int:
            try:
                return int(conn.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0])
            except sqlite3.DatabaseError:
                return -1

        return MeshSemanticCounts(
            reports=count("repository_mesh_reports"),
            targets=count("repository_mesh_targets"),
            observations=count("repository_mesh_observations"),
            actions=count("repository_mesh_actions"),
            plans=count("repository_mesh_reconciliation_plans"),
            receipts=count("repository_mesh_receipts"),
            cycles=count("repository_mesh_runtime_cycles"),
            schedulers=count("repository_mesh_schedulers"),
        )


def semantic_generated_records(sqlite_path: Path) -> tuple[GeneratedFile, ...]:
    return (
        GeneratedFile(
            path=str(sqlite_path),
            description="SQLite semantico atualizado com inventario, reconciliacao e runtime da malha de repositorios.",
            function="repository mesh semantic state",
            file_type="sqlite",
            changed_by="mais_humana.repository_mesh_semantic",
            change_summary="Registradas tabelas compactas da OS permanente de sincronizacao.",
            relation_to_order="000_sincronizacao-dos-espelhos",
        ),
    )


def counts_markdown(counts: MeshSemanticCounts, sqlite_path: Path) -> str:
    lines = [
        "# Repository Mesh Semantic Counts",
        "",
        f"- sqlite: `{sqlite_path}`",
        f"- reports: `{counts.reports}`",
        f"- targets: `{counts.targets}`",
        f"- observations: `{counts.observations}`",
        f"- actions: `{counts.actions}`",
        f"- plans: `{counts.plans}`",
        f"- receipts: `{counts.receipts}`",
        f"- cycles: `{counts.cycles}`",
        f"- schedulers: `{counts.schedulers}`",
        "",
    ]
    return "\n".join(lines)

