# grantflow/tests/test_integration_ingest.py

import gzip
import json

from fastapi.testclient import TestClient

from grantflow.api import runtime_state
from grantflow.api.app import app
import grantflow.api.routes.ingest as ingest_routes

client = TestClient(app)


def test_ingest_inventory_export_endpoint_supports_csv_json_and_gzip(monkeypatch):
    runtime_state.INGEST_AUDIT_STORE.clear()

    def fake_ingest(pdf_path: str, namespace: str, metadata=None):
        return {"namespace": namespace, "source": pdf_path, "chunks_ingested": 1, "stats": {}}

    monkeypatch.setattr(ingest_routes, "ingest_pdf_to_namespace", fake_ingest)

    client.post(
        "/ingest",
        data={
            "donor_id": "usaid",
            "metadata_json": json.dumps({"doc_family": "donor_policy", "source_type": "donor_guidance"}),
        },
        files={"file": ("ads.pdf", b"%PDF-1.4 a", "application/pdf")},
    )
    client.post(
        "/ingest",
        data={
            "donor_id": "usaid",
            "metadata_json": json.dumps({"doc_family": "country_context", "source_type": "country_context"}),
        },
        files={"file": ("kz-context.pdf", b"%PDF-1.4 b", "application/pdf")},
    )

    csv_resp = client.get("/ingest/inventory/export", params={"donor_id": "usaid", "format": "csv"})
    assert csv_resp.status_code == 200
    assert csv_resp.headers["content-type"].startswith("text/csv")
    assert "grantflow_ingest_inventory_usaid.csv" in (csv_resp.headers.get("content-disposition") or "")
    csv_text = csv_resp.text
    assert csv_text.startswith("field,value\n")
    assert "donor_id,usaid" in csv_text
    assert "doc_family_counts.donor_policy,1" in csv_text
    assert "doc_family_counts.country_context,1" in csv_text

    json_resp = client.get("/ingest/inventory/export", params={"donor_id": "usaid", "format": "json"})
    assert json_resp.status_code == 200
    assert json_resp.headers["content-type"].startswith("application/json")
    assert "grantflow_ingest_inventory_usaid.json" in (json_resp.headers.get("content-disposition") or "")
    json_body = json_resp.json()
    assert json_body["donor_id"] == "usaid"
    assert json_body["doc_family_counts"]["donor_policy"] == 1

    csv_gzip_resp = client.get(
        "/ingest/inventory/export", params={"donor_id": "usaid", "format": "csv", "gzip": "true"}
    )
    assert csv_gzip_resp.status_code == 200
    assert csv_gzip_resp.headers["content-type"].startswith("application/gzip")
    assert "grantflow_ingest_inventory_usaid.csv.gz" in (csv_gzip_resp.headers.get("content-disposition") or "")
    csv_gzip_text = gzip.decompress(csv_gzip_resp.content).decode("utf-8")
    assert "doc_family_counts.donor_policy,1" in csv_gzip_text

    json_gzip_resp = client.get(
        "/ingest/inventory/export", params={"donor_id": "usaid", "format": "json", "gzip": "true"}
    )
    assert json_gzip_resp.status_code == 200
    assert json_gzip_resp.headers["content-type"].startswith("application/gzip")
    assert "grantflow_ingest_inventory_usaid.json.gz" in (json_gzip_resp.headers.get("content-disposition") or "")
    json_gzip_body = json.loads(gzip.decompress(json_gzip_resp.content).decode("utf-8"))
    assert json_gzip_body["family_count"] == 2


def test_ingest_endpoint_uploads_to_donor_namespace(monkeypatch):
    runtime_state.INGEST_AUDIT_STORE.clear()
    calls = {}

    def fake_ingest(pdf_path: str, namespace: str, metadata=None):
        calls["pdf_path"] = pdf_path
        calls["namespace"] = namespace
        calls["metadata"] = metadata or {}
        return {
            "namespace": namespace,
            "source": pdf_path,
            "chunks_ingested": 3,
            "stats": {"namespace": namespace, "document_count": 3},
        }

    monkeypatch.setattr(ingest_routes, "ingest_pdf_to_namespace", fake_ingest)

    response = client.post(
        "/ingest",
        data={"donor_id": "usaid", "metadata_json": json.dumps({"source_type": "manual_upload"})},
        files={"file": ("sample.pdf", b"%PDF-1.4 fake content", "application/pdf")},
    )
    assert response.status_code == 200
    body = response.json()
    assert body["status"] == "ingested"
    assert body["donor_id"] == "usaid"
    assert body["namespace"] == "usaid_ads201"
    assert body["namespace_normalized"] == "usaid_ads201"
    assert body["filename"] == "sample.pdf"
    assert body["result"]["chunks_ingested"] == 3

    assert calls["namespace"] == "usaid_ads201"
    assert calls["metadata"]["uploaded_filename"] == "sample.pdf"
    assert calls["metadata"]["donor_id"] == "usaid"
    assert calls["metadata"]["source_type"] == "manual_upload"


def test_ingest_endpoint_applies_tenant_scoped_namespace(monkeypatch):
    runtime_state.INGEST_AUDIT_STORE.clear()
    calls = {}

    def fake_ingest(pdf_path: str, namespace: str, metadata=None):
        calls["namespace"] = namespace
        calls["metadata"] = metadata or {}
        return {
            "namespace": namespace,
            "source": pdf_path,
            "chunks_ingested": 2,
            "stats": {"namespace": namespace, "document_count": 2},
        }

    monkeypatch.setattr(ingest_routes, "ingest_pdf_to_namespace", fake_ingest)

    response = client.post(
        "/ingest",
        data={
            "donor_id": "usaid",
            "tenant_id": "Tenant Alpha",
            "metadata_json": json.dumps({"doc_family": "donor_policy"}),
        },
        files={"file": ("tenant-ads.pdf", b"%PDF-1.4 fake", "application/pdf")},
    )
    assert response.status_code == 200
    body = response.json()
    assert body["tenant_id"] == "tenant_alpha"
    assert body["namespace"] == "tenant_alpha/usaid_ads201"
    assert body["namespace_normalized"] == "tenant_alpha_usaid_ads201"
    assert calls["namespace"] == "tenant_alpha/usaid_ads201"
    assert calls["metadata"]["tenant_id"] == "tenant_alpha"


def test_ingest_recent_endpoint_returns_records(monkeypatch):
    runtime_state.INGEST_AUDIT_STORE.clear()

    def fake_ingest(pdf_path: str, namespace: str, metadata=None):
        return {"namespace": namespace, "source": pdf_path, "chunks_ingested": 1, "stats": {}}

    monkeypatch.setattr(ingest_routes, "ingest_pdf_to_namespace", fake_ingest)

    r1 = client.post(
        "/ingest",
        data={
            "donor_id": "usaid",
            "metadata_json": json.dumps({"doc_family": "donor_policy", "source_type": "donor_guidance"}),
        },
        files={"file": ("ads.pdf", b"%PDF-1.4 a", "application/pdf")},
    )
    assert r1.status_code == 200

    r2 = client.post(
        "/ingest",
        data={
            "donor_id": "eu",
            "metadata_json": json.dumps({"doc_family": "country_context", "source_type": "country_context"}),
        },
        files={"file": ("eu-context.pdf", b"%PDF-1.4 b", "application/pdf")},
    )
    assert r2.status_code == 200

    r3 = client.post(
        "/ingest",
        data={
            "donor_id": "usaid",
            "metadata_json": json.dumps({"doc_family": "country_context", "source_type": "country_context"}),
        },
        files={"file": ("kz-context.pdf", b"%PDF-1.4 c", "application/pdf")},
    )
    assert r3.status_code == 200

    recent = client.get("/ingest/recent", params={"donor_id": "usaid", "limit": 10})
    assert recent.status_code == 200
    body = recent.json()
    assert body["donor_id"] == "usaid"
    assert body["count"] == 2
    assert len(body["records"]) == 2
    assert body["records"][0]["donor_id"] == "usaid"
    assert body["records"][0]["filename"] == "kz-context.pdf"
    assert body["records"][0]["metadata"]["doc_family"] == "country_context"
    assert body["records"][1]["filename"] == "ads.pdf"
    assert body["records"][1]["metadata"]["doc_family"] == "donor_policy"


def test_ingest_inventory_endpoint_aggregates_doc_families(monkeypatch):
    runtime_state.INGEST_AUDIT_STORE.clear()

    def fake_ingest(pdf_path: str, namespace: str, metadata=None):
        return {"namespace": namespace, "source": pdf_path, "chunks_ingested": 1, "stats": {}}

    monkeypatch.setattr(ingest_routes, "ingest_pdf_to_namespace", fake_ingest)

    uploads = [
        ("usaid", "ads201.pdf", {"doc_family": "donor_policy", "source_type": "donor_guidance"}),
        ("usaid", "kazakhstan-context.pdf", {"doc_family": "country_context", "source_type": "country_context"}),
        ("usaid", "ads-addendum.pdf", {"doc_family": "donor_policy", "source_type": "donor_guidance"}),
        ("eu", "eu-guide.pdf", {"doc_family": "donor_policy", "source_type": "donor_guidance"}),
    ]
    for donor_id, filename, metadata in uploads:
        response = client.post(
            "/ingest",
            data={"donor_id": donor_id, "metadata_json": json.dumps(metadata)},
            files={"file": (filename, b"%PDF-1.4 x", "application/pdf")},
        )
        assert response.status_code == 200

    response = client.get("/ingest/inventory", params={"donor_id": "usaid"})
    assert response.status_code == 200
    body = response.json()
    assert body["donor_id"] == "usaid"
    assert body["total_uploads"] == 3
    assert body["family_count"] == 2
    assert body["doc_family_counts"]["donor_policy"] == 2
    assert body["doc_family_counts"]["country_context"] == 1
    families = {row["doc_family"]: row for row in body["doc_families"]}
    assert families["donor_policy"]["count"] == 2
    assert families["donor_policy"]["latest_filename"] in {"ads-addendum.pdf", "ads201.pdf"}
    assert families["country_context"]["count"] == 1
    assert families["country_context"]["latest_filename"] == "kazakhstan-context.pdf"


def test_ingest_inventory_endpoint_filters_by_tenant(monkeypatch):
    runtime_state.INGEST_AUDIT_STORE.clear()

    def fake_ingest(pdf_path: str, namespace: str, metadata=None):
        return {"namespace": namespace, "source": pdf_path, "chunks_ingested": 1, "stats": {}}

    monkeypatch.setattr(ingest_routes, "ingest_pdf_to_namespace", fake_ingest)

    uploads = [
        ("tenant_a", "a-ads.pdf", {"doc_family": "donor_policy", "source_type": "donor_guidance"}),
        ("tenant_b", "b-ads.pdf", {"doc_family": "donor_policy", "source_type": "donor_guidance"}),
        ("tenant_a", "a-country.pdf", {"doc_family": "country_context", "source_type": "country_context"}),
    ]
    for tenant_id, filename, metadata in uploads:
        response = client.post(
            "/ingest",
            data={"donor_id": "usaid", "tenant_id": tenant_id, "metadata_json": json.dumps(metadata)},
            files={"file": (filename, b"%PDF-1.4 x", "application/pdf")},
        )
        assert response.status_code == 200

    response = client.get("/ingest/inventory", params={"donor_id": "usaid", "tenant_id": "tenant_a"})
    assert response.status_code == 200
    body = response.json()
    assert body["tenant_id"] == "tenant_a"
    assert body["total_uploads"] == 2
    assert body["doc_family_counts"]["donor_policy"] == 1
    assert body["doc_family_counts"]["country_context"] == 1
    for row in body["doc_families"]:
        assert row.get("tenant_id") == "tenant_a"


def test_ingest_endpoint_validates_pdf_extension():
    response = client.post(
        "/ingest",
        data={"donor_id": "usaid"},
        files={"file": ("notes.txt", b"hello", "text/plain")},
    )
    assert response.status_code == 400
    detail = response.json()["detail"]
    assert detail["code"] == "unsupported_file_type"
    assert detail["retryable"] is False


def test_ingest_endpoint_requires_api_key_when_configured(monkeypatch):
    def fake_ingest(pdf_path: str, namespace: str, metadata=None):
        return {"namespace": namespace, "source": pdf_path, "chunks_ingested": 1, "stats": {}}

    monkeypatch.setattr(ingest_routes, "ingest_pdf_to_namespace", fake_ingest)
    monkeypatch.setenv("GRANTFLOW_API_KEY", "test-secret")

    response = client.post(
        "/ingest",
        data={"donor_id": "usaid"},
        files={"file": ("sample.pdf", b"%PDF-1.4 fake content", "application/pdf")},
    )
    assert response.status_code == 401

    response = client.post(
        "/ingest",
        data={"donor_id": "usaid"},
        files={"file": ("sample.pdf", b"%PDF-1.4 fake content", "application/pdf")},
        headers={"X-API-Key": "test-secret"},
    )
    assert response.status_code == 200


def test_ingest_text_endpoint_ingests_and_records_event(monkeypatch):
    from grantflow.api import runtime_state

    runtime_state.INGEST_AUDIT_STORE.clear()

    def fake_ingest_text(text, *, namespace, title="document", chunk_size=1000, overlap=200, metadata=None):
        return {
            "namespace": namespace,
            "namespace_normalized": namespace.replace("/", "__"),
            "collection": namespace,
            "source": title,
            "chunks_ingested": 3,
            "stats": {},
        }

    monkeypatch.setattr(ingest_routes, "ingest_text_to_namespace", fake_ingest_text)

    response = client.post(
        "/ingest/text",
        json={
            "text": "Digital governance and e-services pilot context for Moldova.",
            "donor_id": "eu",
            "title": "moldova-pilot-brief",
            "doc_family": "country_context",
        },
    )
    assert response.status_code == 200
    data = response.json()
    assert data["status"] == "ingested"
    assert data["donor_id"] == "eu"
    assert data["title"] == "moldova-pilot-brief"
    assert data["result"]["chunks_ingested"] == 3
    assert data["namespace"]  # namespace was resolved and returned


def test_ingest_text_endpoint_rejects_missing_donor_id():
    response = client.post(
        "/ingest/text",
        json={"text": "Some content", "donor_id": ""},
    )
    assert response.status_code == 400
    detail = response.json()["detail"]
    assert detail["code"] == "missing_donor_id"
    assert detail["retryable"] is False


def test_ingest_text_endpoint_rejects_unsupported_donor():
    response = client.post(
        "/ingest/text",
        json={"text": "Some content", "donor_id": "nonexistent_donor_xyz"},
    )
    assert response.status_code == 400
    detail = response.json()["detail"]
    assert detail["code"] == "unsupported_donor"


def test_ingest_text_endpoint_requires_api_key_when_configured(monkeypatch):
    def fake_ingest_text(text, *, namespace, title="document", chunk_size=1000, overlap=200, metadata=None):
        return {"namespace": namespace, "source": title, "chunks_ingested": 1, "stats": {}}

    monkeypatch.setattr(ingest_routes, "ingest_text_to_namespace", fake_ingest_text)
    monkeypatch.setenv("GRANTFLOW_API_KEY", "test-secret")

    response = client.post(
        "/ingest/text",
        json={"text": "Content", "donor_id": "usaid"},
    )
    assert response.status_code == 401

    response = client.post(
        "/ingest/text",
        json={"text": "Content", "donor_id": "usaid"},
        headers={"X-API-Key": "test-secret"},
    )
    assert response.status_code == 200
