"""SQLite database layer with async support via aiosqlite."""

from __future__ import annotations

import json
import time
from pathlib import Path
from typing import Any

import aiosqlite

from .models import (
    Memory,
    PriceSnapshot,
    Task,
    TaskStatus,
    TaskStep,
    ToolCall,
    TraceEntry,
)

MIGRATIONS_DIR = Path(__file__).parent / "migrations"


class Database:
    """Async SQLite database for AgentScout state persistence."""

    def __init__(self, db_path: str = "agent_scout.db") -> None:
        self.db_path = db_path
        self._conn: aiosqlite.Connection | None = None

    async def connect(self) -> None:
        self._conn = await aiosqlite.connect(self.db_path)
        self._conn.row_factory = aiosqlite.Row
        await self._conn.execute("PRAGMA journal_mode=WAL")
        await self._conn.execute("PRAGMA foreign_keys=ON")
        await self._run_migrations()

    async def close(self) -> None:
        if self._conn:
            await self._conn.close()
            self._conn = None

    @property
    def conn(self) -> aiosqlite.Connection:
        if self._conn is None:
            raise RuntimeError("Database not connected. Call connect() first.")
        return self._conn

    async def _run_migrations(self) -> None:
        """Run any pending SQL migration files."""
        # Ensure schema_migrations table exists (bootstrap)
        await self.conn.execute(
            """CREATE TABLE IF NOT EXISTS schema_migrations (
                version INTEGER PRIMARY KEY,
                applied_at REAL NOT NULL
            )"""
        )
        await self.conn.commit()

        cursor = await self.conn.execute("SELECT COALESCE(MAX(version), 0) FROM schema_migrations")
        row = await cursor.fetchone()
        current_version = row[0] if row else 0

        migration_files = sorted(MIGRATIONS_DIR.glob("*.sql"))
        for mf in migration_files:
            version = int(mf.stem.split("_")[0])
            if version > current_version:
                sql = mf.read_text()
                await self.conn.executescript(sql)
                await self.conn.commit()

    # --- Tasks ---

    async def create_task(self, task: Task) -> Task:
        await self.conn.execute(
            """INSERT INTO tasks
            (id, description, status, autonomy, priority, budget_limit,
             cost_spent, result, error, parent_task_id, created_at, updated_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
            (
                task.id,
                task.description,
                task.status.value,
                task.autonomy.value,
                task.priority,
                task.budget_limit,
                task.cost_spent,
                json.dumps(task.result) if task.result else None,
                task.error,
                task.parent_task_id,
                task.created_at,
                task.updated_at,
            ),
        )
        await self.conn.commit()
        return task

    async def get_task(self, task_id: str) -> Task | None:
        cursor = await self.conn.execute("SELECT * FROM tasks WHERE id = ?", (task_id,))
        row = await cursor.fetchone()
        if not row:
            return None
        return self._row_to_task(row)

    async def update_task_status(self, task_id: str, status: TaskStatus, **kwargs: Any) -> None:
        fields = ["status = ?", "updated_at = ?"]
        values: list[Any] = [status.value, time.time()]
        for key, val in kwargs.items():
            if key == "result":
                fields.append("result = ?")
                values.append(json.dumps(val))
            elif key == "error":
                fields.append("error = ?")
                values.append(val)
            elif key == "cost_spent":
                fields.append("cost_spent = ?")
                values.append(val)
        values.append(task_id)
        await self.conn.execute(
            f"UPDATE tasks SET {', '.join(fields)} WHERE id = ?",  # noqa: S608
            values,
        )
        await self.conn.commit()

    async def list_tasks(
        self,
        status: TaskStatus | None = None,
        limit: int = 50,
    ) -> list[Task]:
        if status:
            cursor = await self.conn.execute(
                "SELECT * FROM tasks WHERE status = ? ORDER BY created_at DESC LIMIT ?",
                (status.value, limit),
            )
        else:
            cursor = await self.conn.execute(
                "SELECT * FROM tasks ORDER BY created_at DESC LIMIT ?", (limit,)
            )
        rows = await cursor.fetchall()
        return [self._row_to_task(row) for row in rows]

    def _row_to_task(self, row: aiosqlite.Row) -> Task:
        return Task(
            id=row["id"],
            description=row["description"],
            status=TaskStatus(row["status"]),
            autonomy=row["autonomy"],
            priority=row["priority"],
            budget_limit=row["budget_limit"],
            cost_spent=row["cost_spent"],
            result=json.loads(row["result"]) if row["result"] else None,
            error=row["error"],
            parent_task_id=row["parent_task_id"],
            created_at=row["created_at"],
            updated_at=row["updated_at"],
        )

    # --- Task Steps ---

    async def create_step(self, step: TaskStep) -> TaskStep:
        await self.conn.execute(
            """INSERT INTO task_steps
            (id, task_id, step_number, thought, action_type, action_data, observation, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
            (
                step.id,
                step.task_id,
                step.step_number,
                step.thought,
                step.action_type,
                json.dumps(step.action_data),
                step.observation,
                step.created_at,
            ),
        )
        await self.conn.commit()
        return step

    async def get_steps(self, task_id: str) -> list[TaskStep]:
        cursor = await self.conn.execute(
            "SELECT * FROM task_steps WHERE task_id = ? ORDER BY step_number",
            (task_id,),
        )
        rows = await cursor.fetchall()
        return [
            TaskStep(
                id=r["id"],
                task_id=r["task_id"],
                step_number=r["step_number"],
                thought=r["thought"],
                action_type=r["action_type"],
                action_data=json.loads(r["action_data"]),
                observation=r["observation"],
                created_at=r["created_at"],
            )
            for r in rows
        ]

    # --- Tool Calls ---

    async def create_tool_call(self, tool_call: ToolCall) -> ToolCall:
        await self.conn.execute(
            """INSERT INTO tool_calls
            (id, task_id, step_id, tool_name, tool_input, tool_output,
             success, duration_ms, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
            (
                tool_call.id,
                tool_call.task_id,
                tool_call.step_id,
                tool_call.tool_name,
                json.dumps(tool_call.tool_input),
                tool_call.tool_output,
                1 if tool_call.success else 0,
                tool_call.duration_ms,
                tool_call.created_at,
            ),
        )
        await self.conn.commit()
        return tool_call

    # --- Memories ---

    async def store_memory(self, memory: Memory) -> Memory:
        await self.conn.execute(
            """INSERT INTO memories
            (id, content, memory_type, tags, relevance_score,
             access_count, last_accessed, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
            (
                memory.id,
                memory.content,
                memory.memory_type,
                json.dumps(memory.tags),
                memory.relevance_score,
                memory.access_count,
                memory.last_accessed,
                memory.created_at,
            ),
        )
        await self.conn.commit()
        return memory

    async def search_memories(
        self,
        memory_type: str | None = None,
        tags: list[str] | None = None,
        limit: int = 20,
    ) -> list[Memory]:
        query = "SELECT * FROM memories"
        conditions: list[str] = []
        params: list[Any] = []

        if memory_type:
            conditions.append("memory_type = ?")
            params.append(memory_type)

        if conditions:
            query += " WHERE " + " AND ".join(conditions)

        query += " ORDER BY relevance_score DESC, last_accessed DESC LIMIT ?"
        params.append(limit)

        cursor = await self.conn.execute(query, params)
        rows = await cursor.fetchall()
        memories = [
            Memory(
                id=r["id"],
                content=r["content"],
                memory_type=r["memory_type"],
                tags=json.loads(r["tags"]),
                relevance_score=r["relevance_score"],
                access_count=r["access_count"],
                last_accessed=r["last_accessed"],
                created_at=r["created_at"],
            )
            for r in rows
        ]

        # Filter by tags in Python (SQLite JSON support is limited)
        if tags:
            memories = [m for m in memories if any(t in m.tags for t in tags)]

        return memories

    async def touch_memory(self, memory_id: str) -> None:
        """Update access count and last_accessed time."""
        await self.conn.execute(
            "UPDATE memories SET access_count = access_count + 1, last_accessed = ? WHERE id = ?",
            (time.time(), memory_id),
        )
        await self.conn.commit()

    async def decay_memories(self, decay_factor: float = 0.95) -> None:
        """Reduce relevance of all memories by a factor (memory decay)."""
        await self.conn.execute(
            "UPDATE memories SET relevance_score = relevance_score * ?",
            (decay_factor,),
        )
        await self.conn.commit()

    # --- Traces ---

    async def store_trace(self, trace: TraceEntry) -> TraceEntry:
        await self.conn.execute(
            """INSERT INTO traces
            (id, task_id, event_type, data, parent_id, timestamp)
            VALUES (?, ?, ?, ?, ?, ?)""",
            (
                trace.id,
                trace.task_id,
                trace.event_type,
                json.dumps(trace.data),
                trace.parent_id,
                trace.timestamp,
            ),
        )
        await self.conn.commit()
        return trace

    async def get_traces(
        self,
        task_id: str | None = None,
        event_type: str | None = None,
        limit: int = 100,
    ) -> list[TraceEntry]:
        query = "SELECT * FROM traces"
        conditions: list[str] = []
        params: list[Any] = []

        if task_id:
            conditions.append("task_id = ?")
            params.append(task_id)
        if event_type:
            conditions.append("event_type = ?")
            params.append(event_type)

        if conditions:
            query += " WHERE " + " AND ".join(conditions)

        query += " ORDER BY timestamp DESC LIMIT ?"
        params.append(limit)

        cursor = await self.conn.execute(query, params)
        rows = await cursor.fetchall()
        return [
            TraceEntry(
                id=r["id"],
                task_id=r["task_id"],
                event_type=r["event_type"],
                data=json.loads(r["data"]),
                parent_id=r["parent_id"],
                timestamp=r["timestamp"],
            )
            for r in rows
        ]

    # --- Price History ---

    async def store_price(self, snapshot: PriceSnapshot) -> PriceSnapshot:
        await self.conn.execute(
            """INSERT INTO price_history
            (id, item_name, source, price, currency, url, metadata, captured_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
            (
                snapshot.id,
                snapshot.item_name,
                snapshot.source,
                snapshot.price,
                snapshot.currency,
                snapshot.url,
                json.dumps(snapshot.metadata),
                snapshot.captured_at,
            ),
        )
        await self.conn.commit()
        return snapshot

    async def get_price_history(self, item_name: str, limit: int = 100) -> list[PriceSnapshot]:
        cursor = await self.conn.execute(
            "SELECT * FROM price_history WHERE item_name = ? ORDER BY captured_at DESC LIMIT ?",
            (item_name, limit),
        )
        rows = await cursor.fetchall()
        return [
            PriceSnapshot(
                id=r["id"],
                item_name=r["item_name"],
                source=r["source"],
                price=r["price"],
                currency=r["currency"],
                url=r["url"],
                metadata=json.loads(r["metadata"]),
                captured_at=r["captured_at"],
            )
            for r in rows
        ]
