"""Tests for the database layer (db.py)."""

from __future__ import annotations

import os
from pathlib import Path

import pytest

from review_mcp import db


# --------------- Schema & init ---------------


async def test_init_db_creates_schema(tmp_db):
    await db.init_db()
    conn = await db.get_db()
    try:
        rows = await conn.execute_fetchall(
            "SELECT name FROM sqlite_master WHERE type='table' AND name='reviews'"
        )
        assert len(rows) == 1
    finally:
        await conn.close()


async def test_init_db_is_idempotent(tmp_db):
    await db.init_db()
    await db.init_db()  # should not raise


# --------------- insert + get ---------------


async def test_insert_review_returns_complete_row(tmp_db):
    await db.init_db()
    r = await db.insert_review("page-a", "alice", "Fix the header")
    assert r["id"] == 1
    assert r["page_id"] == "page-a"
    assert r["author"] == "alice"
    assert r["text"] == "Fix the header"
    assert r["status"] == "open"
    assert r["resolved_at"] is None
    assert r["resolved_by"] is None
    assert r["created_at"] is not None


async def test_get_reviews_all(tmp_db):
    await db.init_db()
    await db.insert_review("page-a", "alice", "Comment 1")
    await db.insert_review("page-b", "bob", "Comment 2")

    rows = await db.get_reviews()
    assert len(rows) == 2


async def test_get_reviews_filtered_by_page(tmp_db):
    await db.init_db()
    await db.insert_review("page-a", "alice", "A1")
    await db.insert_review("page-b", "bob", "B1")
    await db.insert_review("page-a", "alice", "A2")

    rows = await db.get_reviews("page-a")
    assert len(rows) == 2
    assert all(r["page_id"] == "page-a" for r in rows)


async def test_get_reviews_empty_page(tmp_db):
    await db.init_db()
    rows = await db.get_reviews("nonexistent")
    assert rows == []


# --------------- summary ---------------


async def test_get_summary_empty(tmp_db):
    await db.init_db()
    s = await db.get_summary()
    assert s == []


async def test_get_summary_counts(tmp_db):
    await db.init_db()
    await db.insert_review("page-a", "alice", "A1")
    await db.insert_review("page-a", "alice", "A2")
    await db.insert_review("page-b", "bob", "B1")

    # Resolve one on page-a
    await db.update_review(1, status="resolved", resolved_at="now", resolved_by="agent")

    s = await db.get_summary()
    assert len(s) == 2

    page_a = next(r for r in s if r["page_id"] == "page-a")
    assert page_a["open"] == 1
    assert page_a["resolved"] == 1
    assert page_a["total"] == 2

    page_b = next(r for r in s if r["page_id"] == "page-b")
    assert page_b["open"] == 1
    assert page_b["resolved"] == 0
    assert page_b["total"] == 1


# --------------- update ---------------


async def test_update_review_status(tmp_db):
    await db.init_db()
    await db.insert_review("page-a", "alice", "Fix it")

    updated = await db.update_review(1, status="resolved", resolved_at="2026-01-01", resolved_by="agent")
    assert updated["status"] == "resolved"
    assert updated["resolved_at"] == "2026-01-01"
    assert updated["resolved_by"] == "agent"


async def test_update_review_text(tmp_db):
    await db.init_db()
    await db.insert_review("page-a", "alice", "Old text")

    updated = await db.update_review(1, text="New text")
    assert updated["text"] == "New text"
    assert updated["status"] == "open"  # unchanged


async def test_update_review_nonexistent(tmp_db):
    await db.init_db()
    result = await db.update_review(999, status="resolved")
    assert result is None


async def test_update_review_no_valid_fields(tmp_db):
    await db.init_db()
    await db.insert_review("page-a", "alice", "text")
    result = await db.update_review(1, bogus="value")
    assert result is None


# --------------- delete ---------------


async def test_delete_review(tmp_db):
    await db.init_db()
    await db.insert_review("page-a", "alice", "Bye")

    assert await db.delete_review(1) is True
    rows = await db.get_reviews()
    assert len(rows) == 0


async def test_delete_review_nonexistent(tmp_db):
    await db.init_db()
    assert await db.delete_review(999) is False


# --------------- batch_resolve ---------------


async def test_batch_resolve(tmp_db):
    await db.init_db()
    await db.insert_review("page-a", "alice", "A1")
    await db.insert_review("page-a", "alice", "A2")
    await db.insert_review("page-b", "bob", "B1")

    count = await db.batch_resolve("page-a", "bot")
    assert count == 2

    # Verify all page-a are resolved
    rows = await db.get_reviews("page-a")
    assert all(r["status"] == "resolved" for r in rows)
    assert all(r["resolved_by"] == "bot" for r in rows)

    # page-b untouched
    rows_b = await db.get_reviews("page-b")
    assert rows_b[0]["status"] == "open"


async def test_batch_resolve_skips_already_resolved(tmp_db):
    await db.init_db()
    await db.insert_review("page-a", "alice", "A1")
    await db.update_review(1, status="resolved", resolved_at="x", resolved_by="human")

    count = await db.batch_resolve("page-a")
    assert count == 0


# --------------- get_open_reviews_by_page ---------------


async def test_get_open_reviews_by_page(tmp_db):
    await db.init_db()
    await db.insert_review("page-a", "alice", "A1")
    await db.insert_review("page-a", "alice", "A2")
    await db.insert_review("page-b", "bob", "B1")

    # Resolve one
    await db.update_review(2, status="resolved", resolved_at="x", resolved_by="agent")

    pages = await db.get_open_reviews_by_page()
    assert len(pages) == 2

    pa = next(p for p in pages if p["page_id"] == "page-a")
    assert len(pa["reviews"]) == 1  # only A1, A2 is resolved

    pb = next(p for p in pages if p["page_id"] == "page-b")
    assert len(pb["reviews"]) == 1


async def test_get_open_reviews_by_page_all_resolved(tmp_db):
    await db.init_db()
    await db.insert_review("page-a", "alice", "A1")
    await db.batch_resolve("page-a")

    pages = await db.get_open_reviews_by_page()
    assert pages == []


# --------------- _resolve_db_path ---------------


def test_resolve_db_path_explicit(monkeypatch):
    monkeypatch.setenv("REVIEW_DB_PATH", "/custom/path.db")
    monkeypatch.delenv("PROJECT_ROOT", raising=False)
    assert db._resolve_db_path() == "/custom/path.db"


def test_resolve_db_path_project_root(monkeypatch):
    monkeypatch.delenv("REVIEW_DB_PATH", raising=False)
    monkeypatch.setenv("PROJECT_ROOT", "/my/project")
    result = db._resolve_db_path()
    assert ".reviews" in result
    assert result.endswith("reviews.db")


def test_resolve_db_path_fallback(monkeypatch):
    monkeypatch.delenv("REVIEW_DB_PATH", raising=False)
    monkeypatch.delenv("PROJECT_ROOT", raising=False)
    assert db._resolve_db_path() == "./reviews.db"


def test_resolve_db_path_explicit_overrides_project_root(monkeypatch):
    monkeypatch.setenv("REVIEW_DB_PATH", "/explicit.db")
    monkeypatch.setenv("PROJECT_ROOT", "/my/project")
    assert db._resolve_db_path() == "/explicit.db"


# --------------- _ensure_reviews_dir ---------------


async def test_ensure_reviews_dir_creates_files(tmp_path, monkeypatch):
    reviews_dir = tmp_path / ".reviews"
    db_file = str(reviews_dir / "reviews.db")
    monkeypatch.setattr(db, "DB_PATH", db_file)

    await db.init_db()

    assert reviews_dir.exists()
    assert (reviews_dir / ".gitkeep").exists()
    assert (reviews_dir / ".gitignore").exists()

    gitignore_content = (reviews_dir / ".gitignore").read_text()
    assert "*.db-wal" in gitignore_content
    assert "*.db-shm" in gitignore_content


async def test_ensure_reviews_dir_skips_non_reviews_dir(tmp_path, monkeypatch):
    """If DB is not in a .reviews/ dir, don't create .gitkeep etc."""
    db_file = str(tmp_path / "some_other_dir" / "reviews.db")
    monkeypatch.setattr(db, "DB_PATH", db_file)

    # _ensure_reviews_dir should not create parent since name != ".reviews"
    db._ensure_reviews_dir()
    assert not (tmp_path / "some_other_dir").exists()


# --------------- metadata ---------------


async def test_insert_review_with_metadata(tmp_db):
    await db.init_db()
    meta = '{"element":"Button","selector":"#submit-btn"}'
    r = await db.insert_review("page-a", "alice", "Fix button", meta)
    assert r["metadata"] == meta


async def test_insert_review_without_metadata(tmp_db):
    await db.init_db()
    r = await db.insert_review("page-a", "alice", "Fix header")
    assert r["metadata"] is None


async def test_get_reviews_returns_metadata(tmp_db):
    await db.init_db()
    meta = '{"element":"Input","selector":".email-field"}'
    await db.insert_review("page-a", "alice", "Fix input", meta)
    rows = await db.get_reviews("page-a")
    assert rows[0]["metadata"] == meta


async def test_update_review_metadata(tmp_db):
    await db.init_db()
    await db.insert_review("page-a", "alice", "Fix it")
    meta = '{"element":"Div","selector":".card"}'
    updated = await db.update_review(1, metadata=meta)
    assert updated["metadata"] == meta


async def test_migrate_adds_metadata_column(tmp_db):
    """Simulate an old DB without the metadata column and verify migration adds it."""
    import aiosqlite

    # Create old schema without metadata column
    conn = await aiosqlite.connect(tmp_db)
    await conn.executescript("""
        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
        );
    """)
    await conn.execute(
        "INSERT INTO reviews (page_id, author, text, created_at) VALUES (?, ?, ?, ?)",
        ("page-a", "alice", "old review", "2026-01-01"),
    )
    await conn.commit()
    await conn.close()

    # Now init_db should run migration
    await db.init_db()

    # Verify metadata column exists and can be used
    r = await db.insert_review("page-b", "bob", "new", '{"element":"Div"}')
    assert r["metadata"] == '{"element":"Div"}'

    # Old review should have NULL metadata
    rows = await db.get_reviews("page-a")
    assert rows[0]["metadata"] is None
