"""Integration tests for the memorial photo gallery — upload, list, delete.

Covers:
  POST   /api/v1/memorials/{id}/photos
  GET    /api/v1/memorials/{id}         (embeds `photos`)
  DELETE /api/v1/memorials/{id}/photos/{photo_id}
"""
from unittest.mock import MagicMock

import pytest
import pytest_asyncio
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession

import src.apps.memorials.services.memorial_service as memorial_service_module
from src.apps.auth.models.user import User
from src.apps.memorials.models.memorial import Memorial
from src.apps.memorials.models.memorial_photo import MemorialPhoto
from src.apps.records.models.record import Record
from src.apps.tenants.models.account import Account
from src.core.security import build_token_payload, create_access_token, hash_password


# ── helpers ──────────────────────────────────────────────────────────────────
async def _make_account(db, *, subdomain="tenanta"):
    account = Account(
        organization_name="Test Cemetery",
        subdomain=subdomain,
        contact_email=f"admin@{subdomain}.com",
        # Memorials module is gated behind require_feature("memorials") — Starter lacks it.
        plan="professional",
        status="active",
    )
    db.add(account)
    await db.flush()
    return account


async def _make_user(db, account, *, role="administrator"):
    user = User(
        tenant_id=account.id,
        email=f"{role}@{account.subdomain}.com",
        password_hash=hash_password("TestPassword123"),
        first_name=role.capitalize(),
        last_name="User",
        role=role,
        status="active",
    )
    db.add(user)
    await db.flush()
    return user


def _headers(user, account):
    token = create_access_token(build_token_payload(user, account))
    return {"Authorization": f"Bearer {token}"}


async def _make_memorial(db, account, *, slug="patricia"):
    rec = Record(tenant_id=account.id, first_name="Patricia", last_name="O'Brien")
    db.add(rec)
    await db.flush()
    mem = Memorial(tenant_id=account.id, record_id=rec.id, slug=slug)
    db.add(mem)
    await db.flush()
    return mem


@pytest.fixture(autouse=True)
def fake_s3(monkeypatch):
    """Stub the S3 client so photo tests never make real AWS calls."""
    fake_client = MagicMock()
    fake_client.put_object.return_value = {}
    fake_client.delete_object.return_value = {}
    monkeypatch.setattr(memorial_service_module, "_make_s3_client", lambda: fake_client)
    monkeypatch.setattr(memorial_service_module.settings, "AWS_ACCESS_KEY_ID", "fake-key")
    monkeypatch.setattr(memorial_service_module.settings, "S3_DOCUMENTS_BUCKET", "fake-bucket")
    return fake_client


@pytest_asyncio.fixture
async def account(db_session: AsyncSession):
    return await _make_account(db_session)


@pytest_asyncio.fixture
async def admin_headers(db_session, account):
    user = await _make_user(db_session, account, role="administrator")
    return _headers(user, account)


# ── upload ───────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_upload_photo_returns_gallery_url(client: AsyncClient, db_session, account, admin_headers, fake_s3):
    mem = await _make_memorial(db_session, account)

    resp = await client.post(
        f"/api/v1/memorials/{mem.id}/photos",
        headers=admin_headers,
        files={"file": ("photo.jpg", b"fake-image-bytes", "image/jpeg")},
    )
    assert resp.status_code == 201
    data = resp.json()["data"]
    assert data["url"].startswith("/api/public/images/tenant/")
    assert data["sort_order"] == 0
    fake_s3.put_object.assert_called_once()


@pytest.mark.asyncio
async def test_upload_rejects_unsupported_mime_type(client: AsyncClient, db_session, account, admin_headers):
    mem = await _make_memorial(db_session, account)

    resp = await client.post(
        f"/api/v1/memorials/{mem.id}/photos",
        headers=admin_headers,
        files={"file": ("doc.pdf", b"%PDF-1.4", "application/pdf")},
    )
    assert resp.status_code == 422


@pytest.mark.asyncio
async def test_upload_enforces_max_20_photos(client: AsyncClient, db_session, account, admin_headers):
    mem = await _make_memorial(db_session, account)
    for i in range(20):
        db_session.add(
            MemorialPhoto(tenant_id=account.id, memorial_id=mem.id, s3_key=f"k{i}.jpg", sort_order=i)
        )
    await db_session.flush()

    resp = await client.post(
        f"/api/v1/memorials/{mem.id}/photos",
        headers=admin_headers,
        files={"file": ("photo.jpg", b"fake-image-bytes", "image/jpeg")},
    )
    assert resp.status_code == 422
    assert "maximum" in resp.json()["message"].lower()


@pytest.mark.asyncio
async def test_upload_returns_404_for_missing_memorial(client: AsyncClient, db_session, account, admin_headers):
    resp = await client.post(
        "/api/v1/memorials/00000000-0000-0000-0000-000000000000/photos",
        headers=admin_headers,
        files={"file": ("photo.jpg", b"fake-image-bytes", "image/jpeg")},
    )
    assert resp.status_code == 404


@pytest.mark.asyncio
async def test_cross_tenant_upload_returns_404(client: AsyncClient, db_session, account):
    other_account = await _make_account(db_session, subdomain="tenantb")
    other_mem = await _make_memorial(db_session, other_account, slug="other")
    user = await _make_user(db_session, account, role="administrator")
    headers = _headers(user, account)

    resp = await client.post(
        f"/api/v1/memorials/{other_mem.id}/photos",
        headers=headers,
        files={"file": ("photo.jpg", b"fake-image-bytes", "image/jpeg")},
    )
    assert resp.status_code == 404


@pytest.mark.asyncio
async def test_view_only_forbidden_from_uploading(client: AsyncClient, db_session, account):
    mem = await _make_memorial(db_session, account)
    user = await _make_user(db_session, account, role="view_only")
    headers = _headers(user, account)

    resp = await client.post(
        f"/api/v1/memorials/{mem.id}/photos",
        headers=headers,
        files={"file": ("photo.jpg", b"fake-image-bytes", "image/jpeg")},
    )
    assert resp.status_code == 403


# ── list (embedded in GET /memorials/{id}) ────────────────────────────────────
@pytest.mark.asyncio
async def test_get_memorial_includes_photos_ordered_by_sort_order(
    client: AsyncClient, db_session, account, admin_headers
):
    mem = await _make_memorial(db_session, account)
    db_session.add(MemorialPhoto(tenant_id=account.id, memorial_id=mem.id, s3_key="a.jpg", sort_order=1))
    db_session.add(MemorialPhoto(tenant_id=account.id, memorial_id=mem.id, s3_key="b.jpg", sort_order=0))
    await db_session.flush()

    resp = await client.get(f"/api/v1/memorials/{mem.id}", headers=admin_headers)
    assert resp.status_code == 200
    photos = resp.json()["data"]["photos"]
    assert len(photos) == 2
    assert photos[0]["url"].endswith("b.jpg")
    assert photos[1]["url"].endswith("a.jpg")


# ── delete ───────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_delete_photo_removes_it_from_gallery(client: AsyncClient, db_session, account, admin_headers, fake_s3):
    mem = await _make_memorial(db_session, account)
    photo = MemorialPhoto(tenant_id=account.id, memorial_id=mem.id, s3_key="a.jpg", sort_order=0)
    db_session.add(photo)
    await db_session.flush()
    photo_id = photo.id

    resp = await client.delete(f"/api/v1/memorials/{mem.id}/photos/{photo_id}", headers=admin_headers)
    assert resp.status_code == 204
    fake_s3.delete_object.assert_called_once()

    get_resp = await client.get(f"/api/v1/memorials/{mem.id}", headers=admin_headers)
    assert get_resp.json()["data"]["photos"] == []


@pytest.mark.asyncio
async def test_delete_nonexistent_photo_returns_404(client: AsyncClient, db_session, account, admin_headers):
    mem = await _make_memorial(db_session, account)
    resp = await client.delete(
        f"/api/v1/memorials/{mem.id}/photos/00000000-0000-0000-0000-000000000000",
        headers=admin_headers,
    )
    assert resp.status_code == 404


@pytest.mark.asyncio
async def test_delete_photo_from_another_tenant_returns_404(client: AsyncClient, db_session, account, admin_headers):
    other_account = await _make_account(db_session, subdomain="tenantc")
    other_mem = await _make_memorial(db_session, other_account, slug="other2")
    other_photo = MemorialPhoto(tenant_id=other_account.id, memorial_id=other_mem.id, s3_key="x.jpg", sort_order=0)
    db_session.add(other_photo)
    await db_session.flush()

    resp = await client.delete(
        f"/api/v1/memorials/{other_mem.id}/photos/{other_photo.id}", headers=admin_headers
    )
    assert resp.status_code == 404
