"""SQLite database layer for review system (WAL mode, async)."""

from __future__ import annotations

import os
from datetime import datetime, timezone
from pathlib import Path

import aiosqlite

REVIEWS_DIR = ".reviews"


def _resolve_db_path() -> str:
    """Resolve DB path: REVIEW_DB_PATH env > PROJECT_ROOT/.reviews/reviews.db > ./reviews.db"""
    explicit = os.environ.get("REVIEW_DB_PATH")
    if explicit:
        return explicit

    project_root = os.environ.get("PROJECT_ROOT")
    if project_root:
        return str(Path(project_root) / REVIEWS_DIR / "reviews.db")

    return "./reviews.db"


DB_PATH = _resolve_db_path()

SCHEMA = """
CREATE TABLE IF NOT EXISTS reviews (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    page_id     TEXT    NOT NULL,
    author      TEXT    NOT NULL DEFAULT 'anonymous',
    text        TEXT    NOT NULL,
    status      TEXT    NOT NULL DEFAULT 'open',
    created_at  TEXT    NOT NULL,
    resolved_at TEXT,
    resolved_by TEXT,
    metadata    TEXT
);

CREATE INDEX IF NOT EXISTS idx_reviews_page_id ON reviews(page_id);
CREATE INDEX IF NOT EXISTS idx_reviews_status  ON reviews(status);
"""


async def get_db() -> aiosqlite.Connection:
    db = await aiosqlite.connect(DB_PATH)
    await db.execute("PRAGMA journal_mode=WAL")
    await db.execute("PRAGMA busy_timeout=5000")
    db.row_factory = aiosqlite.Row
    return db


async def init_db() -> None:
    _ensure_reviews_dir()
    db = await get_db()
    try:
        await db.executescript(SCHEMA)
        await db.commit()
        await _migrate(db)
    finally:
        await db.close()


async def _migrate(db: aiosqlite.Connection) -> None:
    """Add columns that may be missing in older databases."""
    rows = await db.execute_fetchall("PRAGMA table_info(reviews)")
    columns = {r[1] for r in rows}
    if "metadata" not in columns:
        await db.execute("ALTER TABLE reviews ADD COLUMN metadata TEXT")
        await db.commit()
    if "tag" not in columns:
        await db.execute("ALTER TABLE reviews ADD COLUMN tag TEXT DEFAULT 'general'")
        await db.commit()
    if "parent_id" not in columns:
        await db.execute("ALTER TABLE reviews ADD COLUMN parent_id INTEGER REFERENCES reviews(id)")
        await db.commit()


def _ensure_reviews_dir() -> None:
    """Create .reviews/ directory with .gitkeep if it lives inside a PROJECT_ROOT."""
    db_dir = Path(DB_PATH).parent
    if db_dir.name != REVIEWS_DIR:
        return

    db_dir.mkdir(parents=True, exist_ok=True)

    # .gitkeep so the directory is tracked in git
    gitkeep = db_dir / ".gitkeep"
    if not gitkeep.exists():
        gitkeep.write_text("")

    # .gitignore to track the DB but ignore WAL/SHM temp files
    gitignore = db_dir / ".gitignore"
    if not gitignore.exists():
        gitignore.write_text("*.db-wal\n*.db-shm\n")


# --------------- Queries ---------------


async def get_summary() -> list[dict]:
    """Return per-page summary with open/resolved counts."""
    db = await get_db()
    try:
        sql = """
            SELECT page_id,
                   SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END) AS open,
                   SUM(CASE WHEN status = 'resolved' THEN 1 ELSE 0 END) AS resolved,
                   COUNT(*) AS total
            FROM reviews
            GROUP BY page_id
            ORDER BY page_id
        """
        rows = await db.execute_fetchall(sql)
        return [dict(r) for r in rows]
    finally:
        await db.close()


async def get_reviews(
    page_id: str | None = None, status: str | None = None, tag: str | None = None
) -> list[dict]:
    """Return reviews, optionally filtered by page_id, status, and/or tag."""
    db = await get_db()
    try:
        conditions = []
        params: list = []
        if page_id:
            conditions.append("page_id = ?")
            params.append(page_id)
        if status:
            conditions.append("status = ?")
            params.append(status)
        if tag:
            conditions.append("tag = ?")
            params.append(tag)

        where = f" WHERE {' AND '.join(conditions)}" if conditions else ""
        rows = await db.execute_fetchall(
            f"SELECT * FROM reviews{where} ORDER BY created_at DESC", params
        )
        return [dict(r) for r in rows]
    finally:
        await db.close()


async def insert_review(
    page_id: str,
    author: str,
    text: str,
    metadata: str | None = None,
    tag: str = "general",
    parent_id: int | None = None,
) -> dict:
    """Insert a new review comment and return it."""
    db = await get_db()
    try:
        now = datetime.now(timezone.utc).isoformat()
        cursor = await db.execute(
            "INSERT INTO reviews (page_id, author, text, created_at, metadata, tag, parent_id) VALUES (?, ?, ?, ?, ?, ?, ?)",
            (page_id, author, text, now, metadata, tag, parent_id),
        )
        await db.commit()
        row = await db.execute_fetchall(
            "SELECT * FROM reviews WHERE id = ?", (cursor.lastrowid,)
        )
        return dict(row[0])
    finally:
        await db.close()


async def update_review(review_id: int, **fields) -> dict | None:
    """Update review fields (status, text, resolved_at, resolved_by)."""
    db = await get_db()
    try:
        allowed = {"status", "text", "author", "resolved_at", "resolved_by", "metadata", "tag"}
        updates = {k: v for k, v in fields.items() if k in allowed}
        if not updates:
            return None

        set_clause = ", ".join(f"{k} = ?" for k in updates)
        values = list(updates.values()) + [review_id]

        await db.execute(
            f"UPDATE reviews SET {set_clause} WHERE id = ?", values
        )
        await db.commit()

        row = await db.execute_fetchall(
            "SELECT * FROM reviews WHERE id = ?", (review_id,)
        )
        return dict(row[0]) if row else None
    finally:
        await db.close()


async def delete_review(review_id: int) -> bool:
    """Delete a review. Returns True if deleted."""
    db = await get_db()
    try:
        cursor = await db.execute("DELETE FROM reviews WHERE id = ?", (review_id,))
        await db.commit()
        return cursor.rowcount > 0
    finally:
        await db.close()


async def get_open_reviews_by_page() -> list[dict]:
    """Return open reviews grouped by page_id (for pending work)."""
    db = await get_db()
    try:
        rows = await db.execute_fetchall(
            "SELECT * FROM reviews WHERE status = 'open' ORDER BY page_id, created_at"
        )
        pages: dict[str, list[dict]] = {}
        for r in rows:
            d = dict(r)
            pages.setdefault(d["page_id"], []).append(d)
        return [{"page_id": pid, "reviews": revs} for pid, revs in pages.items()]
    finally:
        await db.close()


async def get_replies(parent_id: int) -> list[dict]:
    """Return replies to a specific review, ordered chronologically."""
    db = await get_db()
    try:
        rows = await db.execute_fetchall(
            "SELECT * FROM reviews WHERE parent_id = ? ORDER BY created_at ASC",
            (parent_id,),
        )
        return [dict(r) for r in rows]
    finally:
        await db.close()


async def batch_resolve(page_id: str, resolved_by: str = "agent") -> int:
    """Resolve all open reviews on a page. Returns count resolved."""
    db = await get_db()
    try:
        now = datetime.now(timezone.utc).isoformat()
        cursor = await db.execute(
            "UPDATE reviews SET status = 'resolved', resolved_at = ?, resolved_by = ? "
            "WHERE page_id = ? AND status = 'open'",
            (now, resolved_by, page_id),
        )
        await db.commit()
        return cursor.rowcount
    finally:
        await db.close()
