"""Backend tests for new features: file uploads, report updates, client replies, access control."""
import os
import io
import pytest
import requests

BASE_URL = os.environ.get("REACT_APP_BACKEND_URL", "https://ayaba-file-upload.preview.emergentagent.com").rstrip("/")
API = f"{BASE_URL}/api"

ADMIN = ("admin@ayabaventures.org", "Admin@2026")
STAFF = ("staff@ayabaventures.org", "Staff@2026")
CLIENT = ("client@example.com", "Client@2026")


def _login(email, password):
    r = requests.post(f"{API}/auth/login", json={"email": email, "password": password}, timeout=20)
    assert r.status_code == 200, f"Login failed for {email}: {r.status_code} {r.text}"
    data = r.json()
    return data["access_token"], data["user"]


def _h(tok):
    return {"Authorization": f"Bearer {tok}"}


# 1x1 PNG bytes (valid image)
PNG_1X1 = bytes.fromhex(
    "89504E470D0A1A0A0000000D49484452000000010000000108060000001F15C4"
    "890000000D49444154789C6300010000000500010D0A2DB40000000049454E44"
    "AE426082"
)


@pytest.fixture(scope="session")
def admin_token():
    tok, _ = _login(*ADMIN)
    return tok


@pytest.fixture(scope="session")
def staff_info():
    tok, user = _login(*STAFF)
    return tok, user


@pytest.fixture(scope="session")
def client_info():
    tok, user = _login(*CLIENT)
    return tok, user


# ---------------- File upload + public serve ----------------
class TestFileUpload:
    def test_upload_image_authenticated(self, admin_token):
        files = {"file": ("test.png", io.BytesIO(PNG_1X1), "image/png")}
        r = requests.post(f"{API}/upload", headers=_h(admin_token), files=files, timeout=60)
        assert r.status_code == 200, r.text
        data = r.json()
        assert "path" in data and "url" in data
        assert data["url"].startswith("/api/files/")
        assert data["content_type"] in ("image/png", "application/octet-stream")
        pytest.upload_path = data["path"]

    def test_upload_requires_auth(self):
        files = {"file": ("test.png", io.BytesIO(PNG_1X1), "image/png")}
        r = requests.post(f"{API}/upload", files=files, timeout=30)
        assert r.status_code == 401

    def test_files_public_no_auth(self, admin_token):
        # First upload, then fetch without auth
        files = {"file": ("public.png", io.BytesIO(PNG_1X1), "image/png")}
        up = requests.post(f"{API}/upload", headers=_h(admin_token), files=files, timeout=60)
        assert up.status_code == 200
        path = up.json()["path"]
        # No auth header
        r = requests.get(f"{API}/files/{path}", timeout=30)
        assert r.status_code == 200, f"Public file fetch failed: {r.status_code}"
        assert r.headers.get("content-type", "").startswith("image/")
        assert len(r.content) > 0

    def test_upload_arbitrary_file_type(self, admin_token):
        files = {"file": ("notes.txt", io.BytesIO(b"hello world"), "text/plain")}
        r = requests.post(f"{API}/upload", headers=_h(admin_token), files=files, timeout=60)
        assert r.status_code == 200, r.text


# ---------------- Seed a project/report for update tests ----------------
@pytest.fixture(scope="session")
def seeded_project(admin_token, staff_info, client_info):
    _, staff_user = staff_info
    _, client_user = client_info
    payload = {
        "client_id": client_user["id"],
        "title": "TEST_Updates Project",
        "location": "Lagos",
        "description": "Test project for update threading",
        "budget": 1000000,
        "progress": 10,
        "status": "active",
        "staff_ids": [staff_user["id"]],
    }
    r = requests.post(f"{API}/admin/projects", headers=_h(admin_token), json=payload, timeout=30)
    assert r.status_code == 200, r.text
    pid = r.json()["id"]

    # Add a report
    rp = requests.post(
        f"{API}/admin/projects/{pid}/reports",
        headers=_h(admin_token),
        json={"title": "TEST_Week 1", "summary": "initial", "week_of": "Week 1", "photos": [], "drone_url": "", "budget_used": 0},
        timeout=30,
    )
    assert rp.status_code == 200, rp.text
    rid = rp.json()["id"]
    return pid, rid


class TestReportUpdates:
    def test_admin_can_post_update(self, admin_token, seeded_project):
        _, rid = seeded_project
        r = requests.post(f"{API}/reports/{rid}/updates", headers=_h(admin_token),
                          json={"body": "Admin update note"}, timeout=20)
        assert r.status_code == 200, r.text
        assert "id" in r.json()
        pytest.admin_update_id = r.json()["id"]

    def test_staff_can_post_update(self, staff_info, seeded_project):
        tok, _ = staff_info
        _, rid = seeded_project
        r = requests.post(f"{API}/reports/{rid}/updates", headers=_h(tok),
                          json={"body": "Staff update from site"}, timeout=20)
        assert r.status_code == 200, r.text
        pytest.staff_update_id = r.json()["id"]

    def test_list_updates_includes_posted(self, admin_token, seeded_project):
        _, rid = seeded_project
        r = requests.get(f"{API}/reports/{rid}/updates", headers=_h(admin_token), timeout=20)
        assert r.status_code == 200
        updates = r.json()
        assert len(updates) >= 2
        bodies = [u["body"] for u in updates]
        assert any("Admin update" in b for b in bodies)
        assert any("Staff update" in b for b in bodies)

    def test_client_reply_threaded(self, client_info, seeded_project):
        tok, _ = client_info
        _, rid = seeded_project
        parent_id = getattr(pytest, "staff_update_id", None)
        assert parent_id, "Staff update must be created first"
        r = requests.post(
            f"{API}/reports/{rid}/updates", headers=_h(tok),
            json={"body": "Client question on staff update", "parent_id": parent_id}, timeout=30,
        )
        assert r.status_code == 200, r.text
        # Verify reply persisted with parent_id
        listr = requests.get(f"{API}/reports/{rid}/updates", headers=_h(tok), timeout=20)
        assert listr.status_code == 200
        ups = listr.json()
        replies = [u for u in ups if u.get("parent_id") == parent_id]
        assert len(replies) >= 1
        assert any(u.get("author_role") == "client" for u in replies)

    def test_invalid_parent_rejected(self, client_info, seeded_project):
        tok, _ = client_info
        _, rid = seeded_project
        r = requests.post(f"{API}/reports/{rid}/updates", headers=_h(tok),
                          json={"body": "bad reply", "parent_id": "507f1f77bcf86cd799439011"}, timeout=20)
        assert r.status_code == 400


class TestAccessControl:
    def test_unassigned_staff_cannot_fetch_reports(self, admin_token, staff_info, client_info):
        # Create a project where the staff is NOT assigned
        _, client_user = client_info
        payload = {
            "client_id": client_user["id"],
            "title": "TEST_Unassigned Project",
            "location": "Abuja",
            "description": "no staff",
            "budget": 500000,
            "progress": 0,
            "status": "active",
            "staff_ids": [],
        }
        r = requests.post(f"{API}/admin/projects", headers=_h(admin_token), json=payload, timeout=20)
        assert r.status_code == 200
        pid = r.json()["id"]
        tok, _ = staff_info
        r2 = requests.get(f"{API}/projects/{pid}/reports", headers=_h(tok), timeout=20)
        assert r2.status_code == 403, f"Expected 403 got {r2.status_code}"

    def test_client_cannot_access_other_project(self, admin_token, client_info):
        # Make project owned by a different client
        ur = requests.post(f"{API}/admin/users", headers=_h(admin_token),
                           json={"email": "TEST_otherclient@example.com", "password": "Other@2026",
                                 "name": "TEST Other", "role": "client"}, timeout=20)
        # Could fail if previously created - that's ok, lookup
        if ur.status_code != 200:
            ul = requests.get(f"{API}/admin/users?role=client", headers=_h(admin_token), timeout=20).json()
            other = next((u for u in ul if u["email"] == "test_otherclient@example.com"), None)
            assert other, "Could not find/create other client"
            other_id = other["id"]
        else:
            other_id = ur.json()["id"]

        proj = requests.post(f"{API}/admin/projects", headers=_h(admin_token), json={
            "client_id": other_id, "title": "TEST_OtherClient Proj", "location": "Kano",
            "description": "x", "budget": 1, "progress": 0, "status": "active", "staff_ids": [],
        }, timeout=20).json()
        pid = proj["id"]

        tok, _ = client_info
        r = requests.get(f"{API}/projects/{pid}/reports", headers=_h(tok), timeout=20)
        assert r.status_code == 403

    def test_admin_can_access_any_project(self, admin_token, seeded_project):
        pid, _ = seeded_project
        r = requests.get(f"{API}/projects/{pid}/reports", headers=_h(admin_token), timeout=20)
        assert r.status_code == 200


class TestProjectReportsListShape:
    def test_reports_include_updates(self, admin_token, seeded_project):
        pid, rid = seeded_project
        r = requests.get(f"{API}/projects/{pid}/reports", headers=_h(admin_token), timeout=20)
        assert r.status_code == 200
        reports = r.json()
        assert len(reports) >= 1
        target = next((rep for rep in reports if rep["id"] == rid), None)
        assert target is not None
        assert "updates" in target and isinstance(target["updates"], list)
        assert len(target["updates"]) >= 2
