"""API tests using in-memory SQLite + FastAPI TestClient — v2 schema aligned."""
from datetime import datetime, timedelta
from pathlib import Path

import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from govcrawler import api as api_pkg
from govcrawler.api.app import _session as real_session
from govcrawler.models import Article, Attachment, Base

from tests._v2fixtures import make_site_and_target


@pytest.fixture
def db(tmp_path, monkeypatch):
    monkeypatch.setenv("DB_URL", "sqlite:///" + str(tmp_path / "t.db"))
    monkeypatch.setenv("DATA_DIR", str(tmp_path / "data"))
    monkeypatch.setenv("USER_AGENT", "TestBot/1.0")

    engine = create_engine("sqlite:///" + str(tmp_path / "t.db"), future=True)
    Base.metadata.create_all(engine)
    SM = sessionmaker(bind=engine, expire_on_commit=False)

    # FastAPI dependency override → inject our test session
    def _override_session():
        with SM() as s:
            yield s

    api_pkg.app.dependency_overrides[real_session] = _override_session

    # Seed site + target + 2 articles; one exported, one not
    with SM() as s:
        site, target = make_site_and_target(
            s, site_code="gdqy", column_id="szfwj",
            entry_url="https://x/list",
        )
        now = datetime(2026, 4, 22, 10, 0, 0)
        a1 = Article(
            id=1,
            site_id=site.id, target_id=target.id,
            url="https://x/1", url_hash="a" * 64,
            title="t1", content_text="正文一" * 30,
            raw_html_path="raw_html/x/1.html",
            text_path="articles_text/x/1.txt", has_attachment=False,
            status="ready", fetch_strategy="httpx", publish_time=now,
            fetched_at=now,
        )
        a2 = Article(
            id=2,
            site_id=site.id, target_id=target.id,
            url="https://x/2", url_hash="b" * 64,
            title="t2", content_text="正文二" * 30,
            raw_html_path="raw_html/x/2.html",
            text_path="articles_text/x/2.txt", has_attachment=True,
            status="ready", fetch_strategy="playwright",
            publish_time=now + timedelta(hours=1),
            fetched_at=now + timedelta(hours=1),
            exported_to_rag_at=now + timedelta(hours=2),   # already acked
        )
        s.add_all([a1, a2])
        s.flush()

        # Attach a file to a2
        data_dir = Path(tmp_path / "data")
        rel = "attachments/gdqy/szfwj/2026/04/a2_notice.pdf"
        p = data_dir / rel
        p.parent.mkdir(parents=True, exist_ok=True)
        p.write_bytes(b"%PDF-FAKE")
        att = Attachment(
            id=1,
            article_id=a2.id, file_name="a2_notice.pdf", file_ext="pdf",
            size_bytes=9, file_path=rel, file_hash="c" * 64,
        )
        s.add(att)
        s.commit()
        ids = {"a1": a1.id, "a2": a2.id, "att": att.id,
               "site_code": site.site_code, "target_code": target.target_code}

    yield SM, ids
    api_pkg.app.dependency_overrides.clear()


def _client():
    return TestClient(api_pkg.app)


def test_health(db):
    r = _client().get("/health")
    assert r.status_code == 200
    assert r.json() == {"status": "ok"}


def test_list_defaults_unexported_only(db):
    _, ids = db
    r = _client().get("/api/articles")
    assert r.status_code == 200
    body = r.json()
    assert body["count"] == 1
    assert body["items"][0]["id"] == ids["a1"]
    # v2 surfaces codes
    assert body["items"][0]["site_code"] == ids["site_code"]
    assert body["items"][0]["target_code"] == ids["target_code"]


def test_list_include_exported(db):
    r = _client().get("/api/articles?only_unexported=false")
    assert r.json()["count"] == 2


def test_list_filter_since(db):
    # since=a2's fetched_at → only a2 (but it's exported, so count=0 by default)
    r = _client().get("/api/articles?since=2026-04-22T10:30:00&only_unexported=false")
    assert r.json()["count"] == 1


def test_list_filter_by_site_code(db):
    _, ids = db
    r = _client().get(f"/api/articles?site={ids['site_code']}&only_unexported=false")
    assert r.json()["count"] == 2
    r2 = _client().get("/api/articles?site=nope&only_unexported=false")
    assert r2.json()["count"] == 0


def test_list_filter_by_target_code(db):
    _, ids = db
    r = _client().get(f"/api/articles?target={ids['target_code']}&only_unexported=false")
    assert r.json()["count"] == 2


def test_get_article_includes_attachments(db):
    _, ids = db
    r = _client().get(f"/api/articles/{ids['a2']}")
    assert r.status_code == 200
    body = r.json()
    assert body["title"] == "t2"
    assert len(body["attachments"]) == 1
    assert body["attachments"][0]["file_ext"] == "pdf"


def test_get_article_404(db):
    r = _client().get("/api/articles/99999")
    assert r.status_code == 404


def test_download_attachment(db):
    _, ids = db
    r = _client().get(f"/api/articles/{ids['a2']}/attachments/{ids['att']}")
    assert r.status_code == 200
    assert r.content == b"%PDF-FAKE"


def test_download_attachment_wrong_article_id(db):
    _, ids = db
    r = _client().get(f"/api/articles/{ids['a1']}/attachments/{ids['att']}")
    assert r.status_code == 404


def test_ack_sets_exported(db):
    SM, ids = db
    r = _client().post(f"/api/articles/{ids['a1']}/ack")
    assert r.status_code == 200
    with SM() as s:
        a = s.get(Article, ids["a1"])
        assert a.exported_to_rag_at is not None

    # Subsequent list no longer returns a1
    r2 = _client().get("/api/articles")
    assert r2.json()["count"] == 0
