# grantflow/tests/test_integration_generation_tenants.py

import time

from fastapi.testclient import TestClient

from grantflow.api.app import app
from grantflow.tests.integration_helpers import wait_for_terminal_status

client = TestClient(app)


def test_generate_with_tenant_id_uses_tenant_scoped_namespace():
    response = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "tenant_id": "Acme Gov",
            "input_context": {"project": "Water Sanitation", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": False,
        },
    )
    assert response.status_code == 200
    job_id = response.json()["job_id"]

    status = wait_for_terminal_status(client, job_id)
    assert status["status"] in {"done", "error", "pending_hitl"}
    state = status.get("state") or {}
    assert state.get("tenant_id") == "acme_gov"
    assert state.get("rag_namespace") == "acme_gov/usaid_ads201"
    retrieval = state.get("architect_retrieval") or {}
    assert retrieval.get("namespace") == "acme_gov/usaid_ads201"
    assert retrieval.get("namespace_normalized") == "acme_gov_usaid_ads201"


def test_generate_requires_allowed_tenant_when_tenant_authz_enabled(monkeypatch):
    monkeypatch.setenv("GRANTFLOW_TENANT_AUTHZ_ENABLED", "true")
    monkeypatch.setenv("GRANTFLOW_ALLOWED_TENANTS", "tenant_alpha")

    missing_tenant = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "input_context": {"project": "Water Sanitation", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": False,
        },
    )
    assert missing_tenant.status_code == 403
    assert "tenant_id" in str(missing_tenant.json().get("detail") or "").lower()

    not_allowed = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "tenant_id": "tenant_beta",
            "input_context": {"project": "Water Sanitation", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": False,
        },
    )
    assert not_allowed.status_code == 403

    allowed = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "tenant_id": "tenant_alpha",
            "input_context": {"project": "Water Sanitation", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": False,
        },
    )
    assert allowed.status_code == 200


def test_status_endpoint_blocks_cross_tenant_when_tenant_authz_enabled(monkeypatch):
    monkeypatch.setenv("GRANTFLOW_TENANT_AUTHZ_ENABLED", "true")
    monkeypatch.setenv("GRANTFLOW_ALLOWED_TENANTS", "tenant_red,tenant_blue")

    generated = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "tenant_id": "tenant_red",
            "input_context": {"project": "Water Sanitation", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": False,
        },
    )
    assert generated.status_code == 200
    job_id = generated.json()["job_id"]

    forbidden = client.get(f"/status/{job_id}", headers={"X-Tenant-ID": "tenant_blue"})
    assert forbidden.status_code == 403

    allowed = client.get(f"/status/{job_id}", headers={"X-Tenant-ID": "tenant_red"})
    assert allowed.status_code == 200


def test_portfolio_metrics_filters_by_tenant_when_tenant_authz_enabled(monkeypatch):
    monkeypatch.setenv("GRANTFLOW_TENANT_AUTHZ_ENABLED", "true")
    monkeypatch.setenv("GRANTFLOW_ALLOWED_TENANTS", "tenant_portfolio_a,tenant_portfolio_b")

    a_job = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "tenant_id": "tenant_portfolio_a",
            "input_context": {"project": "Gov Services A", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": False,
        },
    )
    assert a_job.status_code == 200

    b_job = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "tenant_id": "tenant_portfolio_b",
            "input_context": {"project": "Gov Services B", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": False,
        },
    )
    assert b_job.status_code == 200

    a_metrics = client.get("/portfolio/metrics", headers={"X-Tenant-ID": "tenant_portfolio_a"})
    assert a_metrics.status_code == 200
    a_body = a_metrics.json()
    assert a_body["job_count"] >= 1
    assert a_body["donor_counts"].get("usaid", 0) >= 1

    b_metrics = client.get("/portfolio/metrics", headers={"X-Tenant-ID": "tenant_portfolio_b"})
    assert b_metrics.status_code == 200
    b_body = b_metrics.json()
    assert b_body["job_count"] >= 1
    assert b_body["donor_counts"].get("usaid", 0) >= 1


def test_hitl_pending_filters_by_tenant_when_tenant_authz_enabled(monkeypatch):
    monkeypatch.setenv("GRANTFLOW_TENANT_AUTHZ_ENABLED", "true")
    monkeypatch.setenv("GRANTFLOW_ALLOWED_TENANTS", "tenant_hitl_a,tenant_hitl_b")

    a_job = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "tenant_id": "tenant_hitl_a",
            "input_context": {"project": "HITL A", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": True,
        },
    )
    assert a_job.status_code == 200
    a_job_id = a_job.json()["job_id"]
    deadline = time.time() + 3.0
    while time.time() < deadline:
        a_status = client.get(f"/status/{a_job_id}", headers={"X-Tenant-ID": "tenant_hitl_a"})
        assert a_status.status_code == 200
        if a_status.json()["status"] == "pending_hitl":
            break
        time.sleep(0.05)
    else:
        raise AssertionError("Timed out waiting for tenant A HITL pending state")

    b_job = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "tenant_id": "tenant_hitl_b",
            "input_context": {"project": "HITL B", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": True,
        },
    )
    assert b_job.status_code == 200
    b_job_id = b_job.json()["job_id"]
    deadline = time.time() + 3.0
    while time.time() < deadline:
        b_status = client.get(f"/status/{b_job_id}", headers={"X-Tenant-ID": "tenant_hitl_b"})
        assert b_status.status_code == 200
        if b_status.json()["status"] == "pending_hitl":
            break
        time.sleep(0.05)
    else:
        raise AssertionError("Timed out waiting for tenant B HITL pending state")

    tenant_a_pending = client.get("/hitl/pending", headers={"X-Tenant-ID": "tenant_hitl_a"})
    assert tenant_a_pending.status_code == 200
    tenant_a_body = tenant_a_pending.json()
    assert tenant_a_body["pending_count"] >= 1
    assert all((cp.get("tenant_id") == "tenant_hitl_a") for cp in tenant_a_body["checkpoints"])

    tenant_b_pending = client.get("/hitl/pending", headers={"X-Tenant-ID": "tenant_hitl_b"})
    assert tenant_b_pending.status_code == 200
    tenant_b_body = tenant_b_pending.json()
    assert tenant_b_body["pending_count"] >= 1
    assert all((cp.get("tenant_id") == "tenant_hitl_b") for cp in tenant_b_body["checkpoints"])


def test_status_write_endpoints_block_cross_tenant_when_tenant_authz_enabled(monkeypatch):
    monkeypatch.setenv("GRANTFLOW_TENANT_AUTHZ_ENABLED", "true")
    monkeypatch.setenv("GRANTFLOW_ALLOWED_TENANTS", "tenant_write_a,tenant_write_b")

    generated = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "tenant_id": "tenant_write_a",
            "input_context": {"project": "Cross tenant writes", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": False,
        },
    )
    assert generated.status_code == 200
    job_id = generated.json()["job_id"]

    deadline = time.time() + 3.0
    while time.time() < deadline:
        status = client.get(f"/status/{job_id}", headers={"X-Tenant-ID": "tenant_write_a"})
        assert status.status_code == 200
        if status.json()["status"] in {"done", "error", "pending_hitl"}:
            break
        time.sleep(0.05)
    else:
        raise AssertionError("Timed out waiting for tenant A terminal status")

    cross_tenant_headers = {"X-Tenant-ID": "tenant_write_b"}
    cross_tenant_requests = [
        ("POST", f"/cancel/{job_id}", {}),
        (
            "POST",
            f"/status/{job_id}/comments",
            {"section": "general", "message": "cross tenant create"},
        ),
        ("POST", f"/status/{job_id}/comments/nonexistent/resolve", {}),
        ("POST", f"/status/{job_id}/comments/nonexistent/reopen", {}),
        ("POST", f"/status/{job_id}/critic/findings/nonexistent/ack", {}),
        ("POST", f"/status/{job_id}/critic/findings/nonexistent/open", {}),
        ("POST", f"/status/{job_id}/critic/findings/nonexistent/resolve", {}),
        (
            "POST",
            f"/status/{job_id}/critic/findings/bulk-status",
            {"next_status": "acknowledged", "apply_to_all": True},
        ),
        (
            "POST",
            f"/status/{job_id}/comments/bulk-status",
            {"next_status": "acknowledged", "apply_to_all": True},
        ),
        ("POST", f"/status/{job_id}/review/workflow/sla/recompute", {}),
    ]
    for method, path, payload in cross_tenant_requests:
        response = client.request(method, path, headers=cross_tenant_headers, json=payload)
        assert response.status_code == 403

    owner_comment = client.post(
        f"/status/{job_id}/comments",
        headers={"X-Tenant-ID": "tenant_write_a"},
        json={"section": "general", "message": "owner comment"},
    )
    assert owner_comment.status_code == 200


def test_hitl_write_endpoints_block_cross_tenant_when_tenant_authz_enabled(monkeypatch):
    monkeypatch.setenv("GRANTFLOW_TENANT_AUTHZ_ENABLED", "true")
    monkeypatch.setenv("GRANTFLOW_ALLOWED_TENANTS", "tenant_hitl_write_a,tenant_hitl_write_b")

    generated = client.post(
        "/generate",
        json={
            "donor_id": "usaid",
            "tenant_id": "tenant_hitl_write_a",
            "input_context": {"project": "HITL tenant writes", "country": "Kenya"},
            "llm_mode": False,
            "hitl_enabled": True,
        },
    )
    assert generated.status_code == 200
    job_id = generated.json()["job_id"]

    deadline = time.time() + 3.0
    checkpoint_id = None
    while time.time() < deadline:
        status = client.get(f"/status/{job_id}", headers={"X-Tenant-ID": "tenant_hitl_write_a"})
        assert status.status_code == 200
        body = status.json()
        if body["status"] == "pending_hitl":
            checkpoint_id = body.get("checkpoint_id")
            break
        time.sleep(0.05)
    else:
        raise AssertionError("Timed out waiting for tenant HITL pending state")
    assert checkpoint_id

    cross_headers = {"X-Tenant-ID": "tenant_hitl_write_b"}
    cross_approve = client.post(
        "/hitl/approve",
        headers=cross_headers,
        json={"checkpoint_id": checkpoint_id, "approved": True, "feedback": "cross tenant"},
    )
    assert cross_approve.status_code == 403

    cross_resume = client.post(f"/resume/{job_id}", headers=cross_headers, json={})
    assert cross_resume.status_code == 403

    owner_approve = client.post(
        "/hitl/approve",
        headers={"X-Tenant-ID": "tenant_hitl_write_a"},
        json={"checkpoint_id": checkpoint_id, "approved": True, "feedback": "owner approved"},
    )
    assert owner_approve.status_code == 200

    owner_resume = client.post(f"/resume/{job_id}", headers={"X-Tenant-ID": "tenant_hitl_write_a"}, json={})
    assert owner_resume.status_code == 200
