"""SQLite storage for compact semantic memory."""

from __future__ import annotations

import json
import sqlite3
from pathlib import Path
from typing import Iterable, Sequence

from .models import GeneratedFile, PlatformHumanReport, Recommendation, ServiceOrder, as_plain_data, utc_now
from .operational_models import ExecutionRoundDossier


SCHEMA = """
CREATE TABLE IF NOT EXISTS service_orders (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    order_id TEXT UNIQUE NOT NULL,
    order_type TEXT NOT NULL,
    project_id TEXT NOT NULL,
    title TEXT NOT NULL,
    purpose TEXT NOT NULL,
    object_scope TEXT NOT NULL,
    reason TEXT NOT NULL,
    expected_result TEXT NOT NULL,
    status_semantico TEXT NOT NULL,
    payload_json TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS files (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    caminho_arquivo TEXT UNIQUE NOT NULL,
    descricao TEXT NOT NULL,
    funcao TEXT NOT NULL,
    tipo_arquivo TEXT NOT NULL,
    criado_ou_alterado_por TEXT NOT NULL,
    o_que_mudou TEXT NOT NULL,
    relacao_com_ordem TEXT NOT NULL,
    status_semantico TEXT NOT NULL,
    payload_json TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS platform_reports (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    platform_id TEXT UNIQUE NOT NULL,
    average_score INTEGER NOT NULL,
    code_lines INTEGER NOT NULL,
    evidence_count INTEGER NOT NULL,
    warnings_json TEXT NOT NULL,
    payload_json TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS recommendations (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    recommendation_id TEXT UNIQUE NOT NULL,
    platform_id TEXT NOT NULL,
    title TEXT NOT NULL,
    priority INTEGER NOT NULL,
    order_type TEXT NOT NULL,
    payload_json TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS round_dossiers (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    round_id TEXT UNIQUE NOT NULL,
    project_id TEXT NOT NULL,
    generated_at TEXT NOT NULL,
    blockers_count INTEGER NOT NULL,
    pending_count INTEGER NOT NULL,
    active_orders_count INTEGER NOT NULL,
    output_orders_count INTEGER NOT NULL,
    total_code_lines_analyzed INTEGER NOT NULL,
    code_lines_available_in_project INTEGER NOT NULL,
    payload_json TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS order_justifications (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    order_id TEXT UNIQUE NOT NULL,
    order_type TEXT NOT NULL,
    platform_id TEXT NOT NULL,
    closure_status TEXT NOT NULL,
    reason TEXT NOT NULL,
    execution_summary TEXT NOT NULL,
    pending_count INTEGER NOT NULL,
    linked_signal_count INTEGER NOT NULL,
    linked_gate_count INTEGER NOT NULL,
    resulting_order_count INTEGER NOT NULL,
    payload_json TEXT NOT NULL,
    updated_at TEXT NOT NULL
);
"""


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 upsert_files(conn: sqlite3.Connection, files: Iterable[GeneratedFile], status: str = "atualizado") -> None:
    now = utc_now()
    for item in files:
        payload = json.dumps(as_plain_data(item), ensure_ascii=False, sort_keys=True)
        conn.execute(
            """
            INSERT INTO files (
                caminho_arquivo, descricao, funcao, tipo_arquivo, criado_ou_alterado_por,
                o_que_mudou, relacao_com_ordem, status_semantico, payload_json, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(caminho_arquivo) DO UPDATE SET
                descricao=excluded.descricao,
                funcao=excluded.funcao,
                tipo_arquivo=excluded.tipo_arquivo,
                criado_ou_alterado_por=excluded.criado_ou_alterado_por,
                o_que_mudou=excluded.o_que_mudou,
                relacao_com_ordem=excluded.relacao_com_ordem,
                status_semantico=excluded.status_semantico,
                payload_json=excluded.payload_json,
                updated_at=excluded.updated_at
            """,
            (
                item.path,
                item.description,
                item.function,
                item.file_type,
                item.changed_by,
                item.change_summary,
                item.relation_to_order,
                status,
                payload,
                now,
            ),
        )


def upsert_orders(conn: sqlite3.Connection, orders: Iterable[ServiceOrder]) -> None:
    now = utc_now()
    for order in orders:
        payload = json.dumps(as_plain_data(order), ensure_ascii=False, sort_keys=True)
        conn.execute(
            """
            INSERT INTO service_orders (
                order_id, order_type, project_id, title, purpose, object_scope, reason,
                expected_result, status_semantico, payload_json, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(order_id) DO UPDATE SET
                order_type=excluded.order_type,
                project_id=excluded.project_id,
                title=excluded.title,
                purpose=excluded.purpose,
                object_scope=excluded.object_scope,
                reason=excluded.reason,
                expected_result=excluded.expected_result,
                status_semantico=excluded.status_semantico,
                payload_json=excluded.payload_json,
                updated_at=excluded.updated_at
            """,
            (
                order.order_id,
                order.order_type.value,
                order.project_id,
                order.title,
                order.purpose,
                order.object_scope,
                order.reason,
                order.expected_result,
                order.status.value,
                payload,
                now,
            ),
        )


def upsert_reports(conn: sqlite3.Connection, reports: Sequence[PlatformHumanReport]) -> None:
    now = utc_now()
    for report in reports:
        payload = json.dumps(as_plain_data(report), ensure_ascii=False, sort_keys=True)
        warnings = json.dumps(list(report.scan.warnings), ensure_ascii=False)
        conn.execute(
            """
            INSERT INTO platform_reports (
                platform_id, average_score, code_lines, evidence_count, warnings_json,
                payload_json, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(platform_id) DO UPDATE SET
                average_score=excluded.average_score,
                code_lines=excluded.code_lines,
                evidence_count=excluded.evidence_count,
                warnings_json=excluded.warnings_json,
                payload_json=excluded.payload_json,
                updated_at=excluded.updated_at
            """,
            (
                report.platform.platform_id,
                report.average_score,
                report.scan.code_lines,
                len(report.scan.evidence),
                warnings,
                payload,
                now,
            ),
        )


def upsert_recommendations(conn: sqlite3.Connection, recommendations: Iterable[Recommendation]) -> None:
    now = utc_now()
    for item in recommendations:
        payload = json.dumps(as_plain_data(item), ensure_ascii=False, sort_keys=True)
        conn.execute(
            """
            INSERT INTO recommendations (
                recommendation_id, platform_id, title, priority, order_type, payload_json, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(recommendation_id) DO UPDATE SET
                platform_id=excluded.platform_id,
                title=excluded.title,
                priority=excluded.priority,
                order_type=excluded.order_type,
                payload_json=excluded.payload_json,
                updated_at=excluded.updated_at
            """,
            (
                item.recommendation_id,
                item.platform_id,
                item.title,
                item.priority,
                item.suggested_order_type.value,
                payload,
                now,
            ),
        )


def upsert_round_dossier(conn: sqlite3.Connection, dossier: ExecutionRoundDossier) -> None:
    now = utc_now()
    payload = json.dumps(as_plain_data(dossier), ensure_ascii=False, sort_keys=True)
    conn.execute(
        """
        INSERT INTO round_dossiers (
            round_id, project_id, generated_at, blockers_count, pending_count,
            active_orders_count, output_orders_count, total_code_lines_analyzed,
            code_lines_available_in_project, payload_json, updated_at
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ON CONFLICT(round_id) DO UPDATE SET
            project_id=excluded.project_id,
            generated_at=excluded.generated_at,
            blockers_count=excluded.blockers_count,
            pending_count=excluded.pending_count,
            active_orders_count=excluded.active_orders_count,
            output_orders_count=excluded.output_orders_count,
            total_code_lines_analyzed=excluded.total_code_lines_analyzed,
            code_lines_available_in_project=excluded.code_lines_available_in_project,
            payload_json=excluded.payload_json,
            updated_at=excluded.updated_at
        """,
        (
            dossier.round_id,
            dossier.project_id,
            dossier.generated_at,
            len(dossier.blockers),
            len(dossier.pending_items),
            len(dossier.active_input_orders),
            len(dossier.output_orders),
            dossier.total_code_lines_analyzed,
            dossier.code_lines_available_in_project,
            payload,
            now,
        ),
    )
    for item in dossier.order_justifications:
        item_payload = json.dumps(as_plain_data(item), ensure_ascii=False, sort_keys=True)
        conn.execute(
            """
            INSERT INTO order_justifications (
                order_id, order_type, platform_id, closure_status, reason,
                execution_summary, pending_count, linked_signal_count, linked_gate_count,
                resulting_order_count, payload_json, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON CONFLICT(order_id) DO UPDATE SET
                order_type=excluded.order_type,
                platform_id=excluded.platform_id,
                closure_status=excluded.closure_status,
                reason=excluded.reason,
                execution_summary=excluded.execution_summary,
                pending_count=excluded.pending_count,
                linked_signal_count=excluded.linked_signal_count,
                linked_gate_count=excluded.linked_gate_count,
                resulting_order_count=excluded.resulting_order_count,
                payload_json=excluded.payload_json,
                updated_at=excluded.updated_at
            """,
            (
                item.order_id,
                item.order_type.value,
                item.platform_id,
                item.closure_status.value,
                item.reason,
                item.execution_summary,
                len(item.pending_items),
                len(item.linked_signals),
                len(item.linked_gates),
                len(item.resulting_orders),
                item_payload,
                now,
            ),
        )


def write_semantic_state(
    sqlite_path: Path,
    files: Sequence[GeneratedFile],
    orders: Sequence[ServiceOrder],
    reports: Sequence[PlatformHumanReport],
    recommendations: Sequence[Recommendation],
    round_dossier: ExecutionRoundDossier | None = None,
) -> None:
    with connect(sqlite_path) as conn:
        upsert_files(conn, files)
        upsert_orders(conn, orders)
        upsert_reports(conn, reports)
        upsert_recommendations(conn, recommendations)
        if round_dossier is not None:
            upsert_round_dossier(conn, round_dossier)
        conn.commit()


def table_counts(sqlite_path: Path) -> dict[str, int]:
    if not sqlite_path.exists():
        return {}
    with sqlite3.connect(sqlite_path) as conn:
        counts: dict[str, int] = {}
        for table in ("service_orders", "files", "platform_reports", "recommendations", "round_dossiers", "order_justifications"):
            try:
                counts[table] = int(conn.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0])
            except sqlite3.DatabaseError:
                counts[table] = -1
        return counts
