"""
Share API tests — Phase 6.1, Plan 06.1-01.

Promotes all 7 xfail stubs to real tests covering SHARE-01 through SHARE-05.
"""
from __future__ import annotations

import uuid as _uuid

import pytest
import pytest_asyncio

pytestmark = pytest.mark.asyncio


# ---------------------------------------------------------------------------
# Helper: create an uploaded Document row directly via ORM
# ---------------------------------------------------------------------------


async def _make_doc(db_session, owner_user) -> str:
    """Create an uploaded Document row owned by owner_user and return its str UUID."""
    from db.models import Document

    doc_id = _uuid.uuid4()
    doc = Document(
        id=doc_id,
        user_id=owner_user["user"].id,
        filename="test.txt",
        content_type="text/plain",
        size_bytes=1000,
        storage_backend="minio",
        status="uploaded",
        object_key=f"{owner_user['user'].id}/{doc_id}/{_uuid.uuid4()}.txt",
    )
    db_session.add(doc)
    await db_session.commit()
    return str(doc_id)


# ---------------------------------------------------------------------------
# SHARE-01: Share a document
# ---------------------------------------------------------------------------


async def test_share_success(async_client, auth_user, second_auth_user, db_session):
    """POST /api/shares grants share; recipient can see doc via GET /api/shares/received."""
    doc_id = await _make_doc(db_session, auth_user)

    resp = await async_client.post(
        "/api/shares",
        json={
            "document_id": doc_id,
            "recipient_handle": second_auth_user["user"].handle,
        },
        headers=auth_user["headers"],
    )
    assert resp.status_code == 201, resp.text
    body = resp.json()
    assert "id" in body
    assert body["document_id"] == doc_id
    assert body["recipient_id"] == str(second_auth_user["user"].id)

    # Recipient can see the document in their received list
    received = await async_client.get(
        "/api/shares/received",
        headers=second_auth_user["headers"],
    )
    assert received.status_code == 200
    items = received.json()["items"]
    doc_ids = [item["id"] for item in items]
    assert doc_id in doc_ids


async def test_share_handle_not_found(async_client, auth_user, db_session):
    """POST /api/shares with unknown handle returns 404."""
    doc_id = await _make_doc(db_session, auth_user)

    resp = await async_client.post(
        "/api/shares",
        json={
            "document_id": doc_id,
            "recipient_handle": "nonexistent_handle_xyz",
        },
        headers=auth_user["headers"],
    )
    assert resp.status_code == 404


# ---------------------------------------------------------------------------
# SHARE-02: List shared-with-me
# ---------------------------------------------------------------------------


async def test_shared_with_me(async_client, auth_user, second_auth_user, db_session):
    """GET /api/shares/received lists docs shared with current user (T-04-04-03)."""
    doc_id = await _make_doc(db_session, auth_user)

    # Grant share
    share_resp = await async_client.post(
        "/api/shares",
        json={
            "document_id": doc_id,
            "recipient_handle": second_auth_user["user"].handle,
        },
        headers=auth_user["headers"],
    )
    assert share_resp.status_code == 201

    # Recipient fetches shared-with-me
    received = await async_client.get(
        "/api/shares/received",
        headers=second_auth_user["headers"],
    )
    assert received.status_code == 200
    items = received.json()["items"]
    assert len(items) >= 1

    # Find the specific item
    matching = [item for item in items if item["id"] == doc_id]
    assert len(matching) == 1, f"doc {doc_id} not in received items: {items}"
    item = matching[0]

    # Required metadata fields present
    for field in ("id", "filename", "content_type", "size_bytes", "created_at", "owner_handle"):
        assert field in item, f"Expected field '{field}' in item"

    # T-04-04-03: extracted_text must NEVER be in any item
    for received_item in items:
        assert "extracted_text" not in received_item, (
            "extracted_text must not be returned in shared-with-me responses (T-04-04-03)"
        )

    # owner_handle should match the sharer
    assert item["owner_handle"] == auth_user["user"].handle


# ---------------------------------------------------------------------------
# SHARE-03: Quota isolation
# ---------------------------------------------------------------------------


async def test_share_no_quota_impact(async_client, auth_user, second_auth_user, db_session):
    """Share does not increment recipient's quota used_bytes (T-04-04-04)."""
    doc_id = await _make_doc(db_session, auth_user)

    # Grant share
    share_resp = await async_client.post(
        "/api/shares",
        json={
            "document_id": doc_id,
            "recipient_handle": second_auth_user["user"].handle,
        },
        headers=auth_user["headers"],
    )
    assert share_resp.status_code == 201

    # Check recipient quota — must still be 0 used_bytes
    quota_resp = await async_client.get(
        "/api/auth/me/quota",
        headers=second_auth_user["headers"],
    )
    assert quota_resp.status_code == 200
    quota = quota_resp.json()
    assert quota["used_bytes"] == 0, (
        f"Recipient quota used_bytes should be 0 after share, got {quota['used_bytes']}"
    )


# ---------------------------------------------------------------------------
# SHARE-04: Revoke share
# ---------------------------------------------------------------------------


async def test_revoke_share(async_client, auth_user, second_auth_user, db_session):
    """DELETE /api/shares/{id} removes share; GET /api/shares/received no longer lists the doc."""
    doc_id = await _make_doc(db_session, auth_user)

    # Grant share
    share_resp = await async_client.post(
        "/api/shares",
        json={
            "document_id": doc_id,
            "recipient_handle": second_auth_user["user"].handle,
        },
        headers=auth_user["headers"],
    )
    assert share_resp.status_code == 201
    share_id = share_resp.json()["id"]

    # Revoke share (owner revokes)
    revoke_resp = await async_client.delete(
        f"/api/shares/{share_id}",
        headers=auth_user["headers"],
    )
    assert revoke_resp.status_code == 204

    # Recipient no longer sees the document in received
    received = await async_client.get(
        "/api/shares/received",
        headers=second_auth_user["headers"],
    )
    assert received.status_code == 200
    items = received.json()["items"]
    doc_ids = [item["id"] for item in items]
    assert doc_id not in doc_ids, f"doc {doc_id} should have been revoked but still appears"


async def test_share_revoke_wrong_owner_404(async_client, auth_user, second_auth_user, db_session):
    """DELETE /api/shares/{id} by non-owner returns 404 (IDOR protection — T-04-04-02)."""
    doc_id = await _make_doc(db_session, auth_user)

    # Grant share
    share_resp = await async_client.post(
        "/api/shares",
        json={
            "document_id": doc_id,
            "recipient_handle": second_auth_user["user"].handle,
        },
        headers=auth_user["headers"],
    )
    assert share_resp.status_code == 201
    share_id = share_resp.json()["id"]

    # Recipient attempts to revoke — must be rejected with 404 (not 403)
    revoke_resp = await async_client.delete(
        f"/api/shares/{share_id}",
        headers=second_auth_user["headers"],
    )
    assert revoke_resp.status_code == 404, (
        f"Recipient should get 404 when attempting to revoke a share they don't own, "
        f"got {revoke_resp.status_code}"
    )


# ---------------------------------------------------------------------------
# SHARE-05: Duplicate share
# ---------------------------------------------------------------------------


async def test_share_duplicate(async_client, auth_user, second_auth_user, db_session):
    """POST /api/shares same doc+recipient twice returns 409."""
    doc_id = await _make_doc(db_session, auth_user)

    payload = {
        "document_id": doc_id,
        "recipient_handle": second_auth_user["user"].handle,
    }

    # First share — should succeed
    first_resp = await async_client.post(
        "/api/shares",
        json=payload,
        headers=auth_user["headers"],
    )
    assert first_resp.status_code == 201

    # Second share with same doc+recipient — should return 409
    second_resp = await async_client.post(
        "/api/shares",
        json=payload,
        headers=auth_user["headers"],
    )
    assert second_resp.status_code == 409, (
        f"Duplicate share should return 409, got {second_resp.status_code}"
    )


# ---------------------------------------------------------------------------
# SHARE-03: View-only default permission
# ---------------------------------------------------------------------------


async def test_share_default_permission_view(async_client, auth_user, second_auth_user, db_session):
    """Shares default to permission='view'; owner's share list confirms this (SHARE-03)."""
    doc_id = await _make_doc(db_session, auth_user)

    # Create share — POST response must include permission=view
    resp = await async_client.post(
        "/api/shares",
        json={
            "document_id": doc_id,
            "recipient_handle": second_auth_user["user"].handle,
        },
        headers=auth_user["headers"],
    )
    assert resp.status_code == 201
    body = resp.json()
    assert body["permission"] == "view", (
        f"Expected permission='view' in POST /api/shares response, got {body.get('permission')!r}"
    )

    # GET owner's share list for this doc also reports permission=view
    list_resp = await async_client.get(
        "/api/shares",
        params={"document_id": doc_id},
        headers=auth_user["headers"],
    )
    assert list_resp.status_code == 200
    items = list_resp.json()["items"]
    assert len(items) == 1
    assert items[0]["permission"] == "view", (
        f"Expected permission='view' in share list, got {items[0].get('permission')!r}"
    )


# ---------------------------------------------------------------------------
# SHARE-05: Shared indicator in owner's document list
# ---------------------------------------------------------------------------


async def test_share_indicator_in_owner_list(async_client, auth_user, second_auth_user, db_session):
    """Owner's document list shows is_shared=True after sharing the document (SHARE-05)."""
    doc_id = await _make_doc(db_session, auth_user)

    # Before sharing: is_shared must be False
    pre_resp = await async_client.get("/api/documents", headers=auth_user["headers"])
    assert pre_resp.status_code == 200
    pre_items = pre_resp.json()["items"]
    pre_match = [item for item in pre_items if item["id"] == doc_id]
    assert len(pre_match) == 1
    assert pre_match[0]["is_shared"] is False, (
        f"Expected is_shared=False before sharing, got {pre_match[0].get('is_shared')!r}"
    )

    # Share the document
    share_resp = await async_client.post(
        "/api/shares",
        json={
            "document_id": doc_id,
            "recipient_handle": second_auth_user["user"].handle,
        },
        headers=auth_user["headers"],
    )
    assert share_resp.status_code == 201

    # After sharing: is_shared must be True in owner's document list
    post_resp = await async_client.get("/api/documents", headers=auth_user["headers"])
    assert post_resp.status_code == 200
    post_items = post_resp.json()["items"]
    post_match = [item for item in post_items if item["id"] == doc_id]
    assert len(post_match) == 1
    assert post_match[0]["is_shared"] is True, (
        f"Expected is_shared=True after sharing, got {post_match[0].get('is_shared')!r}"
    )


# ---------------------------------------------------------------------------
# Phase 6.2 Wave 0 xfail stubs — SHARE-03 permission field
# ---------------------------------------------------------------------------


async def test_share_create_with_permission(async_client, auth_user, second_auth_user, db_session):
    """POST /api/shares respects permission field from request body (SHARE-03, D-08, D-10)"""
    doc_id = await _make_doc(db_session, auth_user)

    # POST with explicit permission="edit" — must be stored and returned
    resp = await async_client.post(
        "/api/shares",
        json={
            "document_id": doc_id,
            "recipient_handle": second_auth_user["user"].handle,
            "permission": "edit",
        },
        headers=auth_user["headers"],
    )
    assert resp.status_code == 201, resp.text
    body = resp.json()
    assert body["permission"] == "edit", (
        f"Expected permission='edit' in POST /api/shares response, got {body.get('permission')!r}"
    )

    # POST without permission field defaults to "view"
    # Use a third document to avoid duplicate share constraint
    doc_id2 = await _make_doc(db_session, second_auth_user)
    resp2 = await async_client.post(
        "/api/shares",
        json={
            "document_id": doc_id2,
            "recipient_handle": auth_user["user"].handle,
        },
        headers=second_auth_user["headers"],
    )
    assert resp2.status_code == 201, resp2.text
    body2 = resp2.json()
    assert body2["permission"] == "view", (
        f"Expected default permission='view', got {body2.get('permission')!r}"
    )


async def test_share_patch_permission(async_client, auth_user, second_auth_user, db_session):
    """PATCH /api/shares/{id} changes permission to edit (SHARE-03, D-09)"""
    doc_id = await _make_doc(db_session, auth_user)

    # Create a share with default permission (view)
    share_resp = await async_client.post(
        "/api/shares",
        json={
            "document_id": doc_id,
            "recipient_handle": second_auth_user["user"].handle,
        },
        headers=auth_user["headers"],
    )
    assert share_resp.status_code == 201, share_resp.text
    share_id = share_resp.json()["id"]

    # PATCH to "edit"
    patch_resp = await async_client.patch(
        f"/api/shares/{share_id}",
        json={"permission": "edit"},
        headers=auth_user["headers"],
    )
    assert patch_resp.status_code == 200, patch_resp.text
    assert patch_resp.json()["permission"] == "edit", (
        f"Expected permission='edit' after PATCH, got {patch_resp.json().get('permission')!r}"
    )

    # PATCH back to "view"
    patch_resp2 = await async_client.patch(
        f"/api/shares/{share_id}",
        json={"permission": "view"},
        headers=auth_user["headers"],
    )
    assert patch_resp2.status_code == 200, patch_resp2.text
    assert patch_resp2.json()["permission"] == "view", (
        f"Expected permission='view' after second PATCH, got {patch_resp2.json().get('permission')!r}"
    )


async def test_share_patch_idor(async_client, auth_user, second_auth_user, db_session):
    """PATCH /api/shares/{id} by non-owner returns 404 — IDOR protection (SHARE-03, D-09, T-IDOR)"""
    doc_id = await _make_doc(db_session, auth_user)

    # Create share owned by auth_user
    share_resp = await async_client.post(
        "/api/shares",
        json={
            "document_id": doc_id,
            "recipient_handle": second_auth_user["user"].handle,
        },
        headers=auth_user["headers"],
    )
    assert share_resp.status_code == 201, share_resp.text
    share_id = share_resp.json()["id"]

    # second_auth_user attempts to PATCH a share they do not own — must be 404 (not 403)
    patch_resp = await async_client.patch(
        f"/api/shares/{share_id}",
        json={"permission": "edit"},
        headers=second_auth_user["headers"],
    )
    assert patch_resp.status_code == 404, (
        f"Non-owner should get 404 on PATCH (IDOR protection), got {patch_resp.status_code}"
    )
