"""Tool for SQLite database operations."""

from __future__ import annotations

import json
import sqlite3
from pathlib import Path
from typing import Any

from .base import Tool

_MAX_ROWS = 1000
_MAX_RESULT_CHARS = 200_000


class SqliteTool(Tool):
    """Create, query, and insert into SQLite databases."""

    def __init__(self, *, workspace: Path, allowed_dir: Path | None = None) -> None:
        self._workspace = workspace
        self._allowed_dir = allowed_dir

    @property
    def name(self) -> str:
        return "sqlite"

    @property
    def description(self) -> str:
        return (
            "Interact with SQLite databases: execute SQL queries, create tables, "
            "insert data, and run analytical queries. Databases are stored in the workspace."
        )

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "action": {
                    "type": "string",
                    "enum": ["execute", "query", "schema"],
                    "description": (
                        "execute: run INSERT/UPDATE/DELETE/CREATE. "
                        "query: run SELECT (returns rows). "
                        "schema: list tables and their columns."
                    ),
                },
                "database": {
                    "type": "string",
                    "description": "Database filename (workspace-relative). Created if missing.",
                },
                "sql": {
                    "type": "string",
                    "description": "SQL statement to execute (required for execute/query).",
                },
                "params": {
                    "type": "array",
                    "description": "Positional parameters for parameterized queries.",
                },
            },
            "required": ["action", "database"],
        }

    def _resolve(self, db_name: str) -> Path | str:
        path = (self._workspace / db_name).resolve()
        if self._allowed_dir and not str(path).startswith(str(self._allowed_dir.resolve())):
            return "error"
        if not str(path).startswith(str(self._workspace.resolve())):
            return "error"
        return path

    async def execute(self, **kwargs: Any) -> str:
        action = kwargs.get("action", "query")
        db_name = kwargs.get("database", "")
        sql = kwargs.get("sql", "")
        params = kwargs.get("params") or []

        if not db_name:
            return json.dumps({"error": "database is required"})

        path = self._resolve(db_name)
        if path == "error":
            return json.dumps({"error": "Path outside allowed directory"})

        path.parent.mkdir(parents=True, exist_ok=True)

        try:
            conn = sqlite3.connect(str(path))
            conn.row_factory = sqlite3.Row

            if action == "schema":
                return self._schema(conn, path)
            if action == "execute":
                return self._execute(conn, sql, params, path)
            if action == "query":
                return self._query(conn, sql, params, path)
            return json.dumps({"error": f"Unknown action: {action}"})
        except sqlite3.Error as exc:
            return json.dumps({"error": f"SQLite error: {exc}"})
        finally:
            conn.close()

    def _schema(self, conn: sqlite3.Connection, path: Path) -> str:
        cur = conn.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
        tables = []
        for row in cur.fetchall():
            tname = row[0]
            cols_cur = conn.execute(f"PRAGMA table_info({tname})")  # noqa: S608
            cols = [
                {"name": c[1], "type": c[2], "notnull": bool(c[3]), "pk": bool(c[5])}
                for c in cols_cur.fetchall()
            ]
            count_cur = conn.execute(f"SELECT COUNT(*) FROM [{tname}]")  # noqa: S608
            row_count = count_cur.fetchone()[0]
            tables.append({"name": tname, "columns": cols, "row_count": row_count})
        return json.dumps({"ok": True, "database": str(path), "tables": tables})

    def _execute(self, conn: sqlite3.Connection, sql: str, params: list, path: Path) -> str:
        if not sql.strip():
            return json.dumps({"error": "sql is required for execute action"})
        cur = conn.execute(sql, params)
        conn.commit()
        return json.dumps({
            "ok": True,
            "database": str(path),
            "changes": cur.rowcount,
            "lastrowid": cur.lastrowid,
        })

    def _query(self, conn: sqlite3.Connection, sql: str, params: list, path: Path) -> str:
        if not sql.strip():
            return json.dumps({"error": "sql is required for query action"})
        cur = conn.execute(sql, params)
        columns = [desc[0] for desc in cur.description] if cur.description else []
        rows = []
        for row in cur.fetchmany(_MAX_ROWS):
            rows.append(dict(zip(columns, row)))
        result = json.dumps({
            "ok": True,
            "database": str(path),
            "columns": columns,
            "row_count": len(rows),
            "rows": rows,
        })
        if len(result) > _MAX_RESULT_CHARS:
            result = result[:_MAX_RESULT_CHARS] + '..."}'
        return result
