from __future__ import annotations

import json
import sqlite3
import time
from datetime import datetime
from functools import wraps
from typing import Any, Callable, Dict, List, Optional, Tuple, Union


def _now_iso() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def request_dict(args: Any) -> Dict[str, Any]:
    return {k: v for k, v in vars(args).items() if k not in ("func",) and not k.startswith("_")}


def write_operation_log(
    conn: sqlite3.Connection,
    *,
    session_id: Optional[str],
    skill: str,
    command: str,
    op: str,
    table_name: str,
    entity_id: Optional[str],
    request_json: Optional[Dict[str, Any]],
    before_json: Optional[Dict[str, Any]],
    after_json: Optional[Dict[str, Any]],
    result: str,
    error_code: Optional[str],
    confirmed: bool,
    duration_ms: int,
) -> None:
    conn.execute(
        """
        INSERT INTO store_operation_log (
            occurred_at, session_id, skill, command, op, table_name, entity_id,
            request_json, before_json, after_json, result, error_code, confirmed, duration_ms
        ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
        """,
        (
            _now_iso(),
            session_id or "",
            skill,
            command,
            op,
            table_name,
            entity_id,
            json.dumps(request_json or {}, ensure_ascii=False),
            json.dumps(before_json, ensure_ascii=False) if before_json is not None else None,
            json.dumps(after_json, ensure_ascii=False) if after_json is not None else None,
            result,
            error_code,
            1 if confirmed else 0,
            duration_ms,
        ),
    )


def commit_with_log(
    conn: sqlite3.Connection,
    args: Any,
    *,
    skill: str,
    command: str,
    op: str,
    table_name: str,
    entity_id: Optional[str] = None,
    before_json: Optional[Dict[str, Any]] = None,
    after_json: Optional[Dict[str, Any]] = None,
    duration_ms: int = 0,
) -> None:
    write_operation_log(
        conn,
        session_id=getattr(args, "session_id", None),
        skill=skill,
        command=command,
        op=op,
        table_name=table_name,
        entity_id=entity_id,
        request_json=request_dict(args),
        before_json=before_json,
        after_json=after_json,
        result="ok",
        error_code=None,
        confirmed=bool(getattr(args, "yes", False)),
        duration_ms=duration_ms,
    )
    conn.commit()


def log_read(
    conn: sqlite3.Connection,
    args: Any,
    *,
    skill: str,
    command: str,
    table_name: str,
    entity_id: Optional[str] = None,
    duration_ms: int = 0,
) -> None:
    write_operation_log(
        conn,
        session_id=getattr(args, "session_id", None),
        skill=skill,
        command=command,
        op="read",
        table_name=table_name,
        entity_id=entity_id,
        request_json=request_dict(args),
        before_json=None,
        after_json=None,
        result="ok",
        error_code=None,
        confirmed=False,
        duration_ms=duration_ms,
    )
    conn.commit()


def log_cli_command(
    *,
    skill: str,
    command: str,
    op: str,
    table_name: str,
    get_entity_id: Optional[Callable[[Any, Dict[str, Any]], Optional[str]]] = None,
):
    """Decorator for CLI handlers: conn is first arg after args namespace."""

    def deco(fn: Callable):
        @wraps(fn)
        def wrapper(args, conn: sqlite3.Connection, *rest):
            t0 = time.perf_counter()
            request = {k: v for k, v in vars(args).items() if not k.startswith("_")}
            session_id = getattr(args, "session_id", None)
            confirmed = bool(getattr(args, "yes", False))
            try:
                result_payload = fn(args, conn, *rest)
                duration = int((time.perf_counter() - t0) * 1000)
                entity_id = get_entity_id(args, result_payload) if get_entity_id and isinstance(result_payload, dict) else None
                write_operation_log(
                    conn,
                    session_id=session_id,
                    skill=skill,
                    command=command,
                    op=op,
                    table_name=table_name,
                    entity_id=entity_id,
                    request_json=request,
                    before_json=None,
                    after_json=result_payload if isinstance(result_payload, dict) else None,
                    result="ok",
                    error_code=None,
                    confirmed=confirmed,
                    duration_ms=duration,
                )
                conn.commit()
                return result_payload
            except Exception as exc:
                duration = int((time.perf_counter() - t0) * 1000)
                code = getattr(exc, "error_code", None) or str(exc)
                write_operation_log(
                    conn,
                    session_id=session_id,
                    skill=skill,
                    command=command,
                    op=op,
                    table_name=table_name,
                    entity_id=None,
                    request_json=request,
                    before_json=None,
                    after_json=None,
                    result="error",
                    error_code=str(code),
                    confirmed=confirmed,
                    duration_ms=duration,
                )
                conn.commit()
                raise

        return wrapper

    return deco
