"""Integration tests for consent endpoints.

Covers auth/authz, ownership gating, audit trail, idempotency,
partial updates, admin access, pagination, and 404 handling.
"""

import uuid

import pytest

BASE = "/api/v1/consent"


# ── Auth tests ───────────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_unauthenticated_get_returns_401(client):
    """GET /consent/videos without token → 401."""
    resp = await client.get(f"{BASE}/videos")
    assert resp.status_code == 401


@pytest.mark.asyncio
async def test_unauthenticated_put_returns_401(client):
    """PUT /consent/videos/{id} without token → 401."""
    resp = await client.put(
        f"{BASE}/videos/{uuid.uuid4()}", json={"kb_inclusion": True}
    )
    assert resp.status_code == 401


@pytest.mark.asyncio
async def test_user_without_creator_id_list_returns_403(client, auth_headers):
    """User with no creator_id → 403 on GET /consent/videos."""
    resp = await client.get(f"{BASE}/videos", headers=auth_headers)
    assert resp.status_code == 403


@pytest.mark.asyncio
async def test_user_without_creator_id_get_single_returns_403(
    client, auth_headers, creator_with_videos
):
    """User with no creator_id → 403 on GET /consent/videos/{id}."""
    vid = str(creator_with_videos["video_ids"][0])
    resp = await client.get(f"{BASE}/videos/{vid}", headers=auth_headers)
    assert resp.status_code == 403


@pytest.mark.asyncio
async def test_user_without_creator_id_put_returns_403(
    client, auth_headers, creator_with_videos
):
    """User with no creator_id → 403 on PUT /consent/videos/{id}."""
    vid = str(creator_with_videos["video_ids"][0])
    resp = await client.put(
        f"{BASE}/videos/{vid}", json={"kb_inclusion": True}, headers=auth_headers
    )
    assert resp.status_code == 403


# ── Ownership tests ──────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_creator_cannot_read_other_creators_video(
    client, db_engine, creator_user_auth
):
    """Creator A can't GET consent for Creator B's video → 403."""
    from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
    from models import Creator, SourceVideo, ContentType, ProcessingStatus

    factory = async_sessionmaker(db_engine, class_=AsyncSession, expire_on_commit=False)
    async with factory() as session:
        other_creator = Creator(
            name="OtherCreator", slug="othercreator", folder_name="Other"
        )
        session.add(other_creator)
        await session.flush()
        other_video = SourceVideo(
            creator_id=other_creator.id,
            filename="other.mp4",
            file_path="Other/other.mp4",
            duration_seconds=100,
            content_type=ContentType.tutorial,
            processing_status=ProcessingStatus.not_started,
        )
        session.add(other_video)
        await session.flush()
        other_vid = str(other_video.id)
        await session.commit()

    resp = await client.get(
        f"{BASE}/videos/{other_vid}", headers=creator_user_auth["headers"]
    )
    assert resp.status_code == 403


@pytest.mark.asyncio
async def test_creator_cannot_update_other_creators_video(
    client, db_engine, creator_user_auth
):
    """Creator A can't PUT consent for Creator B's video → 403."""
    from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
    from models import Creator, SourceVideo, ContentType, ProcessingStatus

    factory = async_sessionmaker(db_engine, class_=AsyncSession, expire_on_commit=False)
    async with factory() as session:
        other_creator = Creator(
            name="OtherCreator2", slug="othercreator2", folder_name="Other2"
        )
        session.add(other_creator)
        await session.flush()
        other_video = SourceVideo(
            creator_id=other_creator.id,
            filename="other2.mp4",
            file_path="Other2/other2.mp4",
            duration_seconds=100,
            content_type=ContentType.tutorial,
            processing_status=ProcessingStatus.not_started,
        )
        session.add(other_video)
        await session.flush()
        other_vid = str(other_video.id)
        await session.commit()

    resp = await client.put(
        f"{BASE}/videos/{other_vid}",
        json={"kb_inclusion": True},
        headers=creator_user_auth["headers"],
    )
    assert resp.status_code == 403


# ── Happy path — PUT ─────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_put_creates_consent_and_audit_entries(client, creator_user_auth):
    """First PUT creates VideoConsent + audit entries with correct flags."""
    vid = str(creator_user_auth["video_ids"][0])
    resp = await client.put(
        f"{BASE}/videos/{vid}",
        json={"kb_inclusion": True, "training_usage": True},
        headers=creator_user_auth["headers"],
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["kb_inclusion"] is True
    assert data["training_usage"] is True
    # public_display defaults to True
    assert data["public_display"] is True
    assert data["source_video_id"] == vid


# ── Happy path — GET list ────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_get_list_returns_creator_videos(client, creator_user_auth):
    """GET /consent/videos returns consents for the creator's videos."""
    # Create consent for one video first
    vid = str(creator_user_auth["video_ids"][0])
    await client.put(
        f"{BASE}/videos/{vid}",
        json={"kb_inclusion": True},
        headers=creator_user_auth["headers"],
    )

    resp = await client.get(
        f"{BASE}/videos", headers=creator_user_auth["headers"]
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["total"] >= 1
    assert len(data["items"]) >= 1
    # Verify our video is in the results
    video_ids_in_results = [item["source_video_id"] for item in data["items"]]
    assert vid in video_ids_in_results


@pytest.mark.asyncio
async def test_get_list_pagination(client, creator_user_auth):
    """GET /consent/videos respects offset/limit pagination."""
    # Create consent for both videos
    for vid in creator_user_auth["video_ids"]:
        await client.put(
            f"{BASE}/videos/{str(vid)}",
            json={"kb_inclusion": True},
            headers=creator_user_auth["headers"],
        )

    # Fetch with limit=1
    resp = await client.get(
        f"{BASE}/videos?limit=1&offset=0",
        headers=creator_user_auth["headers"],
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["total"] == 2
    assert len(data["items"]) == 1

    # Fetch page 2
    resp2 = await client.get(
        f"{BASE}/videos?limit=1&offset=1",
        headers=creator_user_auth["headers"],
    )
    assert resp2.status_code == 200
    data2 = resp2.json()
    assert len(data2["items"]) == 1
    # Different video than page 1
    assert data2["items"][0]["source_video_id"] != data["items"][0]["source_video_id"]


# ── Happy path — GET single ──────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_get_single_returns_defaults_when_no_consent(client, creator_user_auth):
    """GET /consent/videos/{id} returns defaults when no consent exists."""
    vid = str(creator_user_auth["video_ids"][0])
    resp = await client.get(
        f"{BASE}/videos/{vid}", headers=creator_user_auth["headers"]
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["kb_inclusion"] is False
    assert data["training_usage"] is False
    assert data["public_display"] is True


@pytest.mark.asyncio
async def test_get_single_returns_updated_consent(client, creator_user_auth):
    """GET returns the consent values after a PUT."""
    vid = str(creator_user_auth["video_ids"][0])
    await client.put(
        f"{BASE}/videos/{vid}",
        json={"kb_inclusion": True, "public_display": False},
        headers=creator_user_auth["headers"],
    )
    resp = await client.get(
        f"{BASE}/videos/{vid}", headers=creator_user_auth["headers"]
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["kb_inclusion"] is True
    assert data["training_usage"] is False
    assert data["public_display"] is False


# ── Audit trail ──────────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_audit_trail_records_field_changes(client, creator_user_auth):
    """PUT changing 2 fields creates 2 audit entries with correct versions."""
    vid = str(creator_user_auth["video_ids"][0])
    await client.put(
        f"{BASE}/videos/{vid}",
        json={"kb_inclusion": True, "training_usage": True},
        headers=creator_user_auth["headers"],
    )

    resp = await client.get(
        f"{BASE}/videos/{vid}/history", headers=creator_user_auth["headers"]
    )
    assert resp.status_code == 200
    entries = resp.json()
    assert len(entries) == 2
    # Versions should be incrementing
    versions = [e["version"] for e in entries]
    assert versions == [1, 2]
    # Both entries should have new_value=True
    for entry in entries:
        assert entry["new_value"] is True
    # Fields should cover both changed fields
    field_names = {e["field_name"] for e in entries}
    assert field_names == {"kb_inclusion", "training_usage"}


@pytest.mark.asyncio
async def test_audit_trail_ordered_by_version(client, creator_user_auth):
    """Successive PUTs create audit entries in version order."""
    vid = str(creator_user_auth["video_ids"][0])
    # First change
    await client.put(
        f"{BASE}/videos/{vid}",
        json={"kb_inclusion": True},
        headers=creator_user_auth["headers"],
    )
    # Second change
    await client.put(
        f"{BASE}/videos/{vid}",
        json={"kb_inclusion": False},
        headers=creator_user_auth["headers"],
    )

    resp = await client.get(
        f"{BASE}/videos/{vid}/history", headers=creator_user_auth["headers"]
    )
    entries = resp.json()
    assert len(entries) == 2
    assert entries[0]["version"] < entries[1]["version"]
    assert entries[0]["new_value"] is True
    assert entries[1]["new_value"] is False
    assert entries[1]["old_value"] is True


# ── Idempotency ──────────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_idempotent_put_creates_no_audit_entries(client, creator_user_auth):
    """PUT with same values as current state → no new audit entries."""
    vid = str(creator_user_auth["video_ids"][0])
    # Set initial values
    await client.put(
        f"{BASE}/videos/{vid}",
        json={"kb_inclusion": True},
        headers=creator_user_auth["headers"],
    )
    # Same PUT again
    await client.put(
        f"{BASE}/videos/{vid}",
        json={"kb_inclusion": True},
        headers=creator_user_auth["headers"],
    )

    resp = await client.get(
        f"{BASE}/videos/{vid}/history", headers=creator_user_auth["headers"]
    )
    entries = resp.json()
    # Only 1 audit entry from the first PUT, none from the duplicate
    assert len(entries) == 1


# ── Partial update ───────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_partial_update_changes_only_specified_field(client, creator_user_auth):
    """PUT with only kb_inclusion changes only that field, leaves others."""
    vid = str(creator_user_auth["video_ids"][0])
    # Set all three fields
    await client.put(
        f"{BASE}/videos/{vid}",
        json={"kb_inclusion": True, "training_usage": True, "public_display": False},
        headers=creator_user_auth["headers"],
    )
    # Now change only kb_inclusion
    resp = await client.put(
        f"{BASE}/videos/{vid}",
        json={"kb_inclusion": False},
        headers=creator_user_auth["headers"],
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["kb_inclusion"] is False
    assert data["training_usage"] is True  # unchanged
    assert data["public_display"] is False  # unchanged


# ── Admin access ─────────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_admin_can_read_any_creators_consent(
    client, creator_user_auth, admin_auth
):
    """Admin can GET consent for any creator's video."""
    vid = str(creator_user_auth["video_ids"][0])
    # Creator sets consent
    await client.put(
        f"{BASE}/videos/{vid}",
        json={"kb_inclusion": True},
        headers=creator_user_auth["headers"],
    )
    # Admin reads it
    resp = await client.get(
        f"{BASE}/videos/{vid}", headers=admin_auth["headers"]
    )
    assert resp.status_code == 200
    assert resp.json()["kb_inclusion"] is True


@pytest.mark.asyncio
async def test_admin_summary_returns_correct_counts(
    client, creator_user_auth, admin_auth
):
    """Admin summary endpoint returns aggregate consent counts."""
    # Set consent on both videos
    for vid in creator_user_auth["video_ids"]:
        await client.put(
            f"{BASE}/videos/{str(vid)}",
            json={"kb_inclusion": True, "training_usage": False, "public_display": True},
            headers=creator_user_auth["headers"],
        )

    resp = await client.get(
        f"{BASE}/admin/summary", headers=admin_auth["headers"]
    )
    assert resp.status_code == 200
    data = resp.json()
    assert data["total_videos"] == 2
    assert data["kb_inclusion_granted"] == 2
    assert data["training_usage_granted"] == 0
    assert data["public_display_granted"] == 2


@pytest.mark.asyncio
async def test_non_admin_cannot_access_summary(client, creator_user_auth):
    """Non-admin user → 403 on admin summary endpoint."""
    resp = await client.get(
        f"{BASE}/admin/summary", headers=creator_user_auth["headers"]
    )
    assert resp.status_code == 403


# ── Nonexistent video ────────────────────────────────────────────────────────


@pytest.mark.asyncio
async def test_get_nonexistent_video_returns_404(client, creator_user_auth):
    """GET /consent/videos/{random_uuid} → 404."""
    resp = await client.get(
        f"{BASE}/videos/{uuid.uuid4()}", headers=creator_user_auth["headers"]
    )
    assert resp.status_code == 404


@pytest.mark.asyncio
async def test_put_nonexistent_video_returns_404(client, creator_user_auth):
    """PUT /consent/videos/{random_uuid} → 404."""
    resp = await client.put(
        f"{BASE}/videos/{uuid.uuid4()}",
        json={"kb_inclusion": True},
        headers=creator_user_auth["headers"],
    )
    assert resp.status_code == 404


@pytest.mark.asyncio
async def test_history_nonexistent_video_returns_404(client, creator_user_auth):
    """GET /consent/videos/{random_uuid}/history → 404."""
    resp = await client.get(
        f"{BASE}/videos/{uuid.uuid4()}/history",
        headers=creator_user_auth["headers"],
    )
    assert resp.status_code == 404
