"""Additional DMS error-path tests to boost coverage above 80%."""

from __future__ import annotations

import uuid

import pytest

from tests.conftest import ORIGIN_HEADER

PDF_CONTENT = b"%PDF-1.4\n1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n%%EOF"


class TestFolderErrorPaths:
    """Error path tests for folder endpoints."""

    @pytest.mark.asyncio
    async def test_create_folder_duplicate_name(self, authed_client):
        """POST /folders → 409 duplicate name."""
        client, _ = authed_client
        await client.post("/api/v1/dms/folders", json={"name": "Dup"}, headers=ORIGIN_HEADER)
        resp = await client.post("/api/v1/dms/folders", json={"name": "Dup"}, headers=ORIGIN_HEADER)
        assert resp.status_code == 409

    @pytest.mark.asyncio
    async def test_create_folder_parent_not_found(self, authed_client):
        """POST /folders → 404 parent not found."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/folders",
            json={"name": "Child", "parent_id": str(uuid.uuid4())},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_create_folder_invalid_parent_id(self, authed_client):
        """POST /folders → 400 invalid parent_id."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/folders",
            json={"name": "Child", "parent_id": "not-a-uuid"},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 400

    @pytest.mark.asyncio
    async def test_list_folders_with_parent_id(self, authed_client):
        """GET /folders?parent_id → children of specified parent."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/folders", json={"name": "Root"}, headers=ORIGIN_HEADER
        )
        root_id = resp.json()["id"]
        await client.post(
            "/api/v1/dms/folders",
            json={"name": "Child1", "parent_id": root_id},
            headers=ORIGIN_HEADER,
        )
        await client.post(
            "/api/v1/dms/folders",
            json={"name": "Child2", "parent_id": root_id},
            headers=ORIGIN_HEADER,
        )

        resp = await client.get(f"/api/v1/dms/folders?parent_id={root_id}", headers=ORIGIN_HEADER)
        assert resp.status_code == 200
        children = resp.json()
        assert len(children) == 2

    @pytest.mark.asyncio
    async def test_list_folders_parent_not_found(self, authed_client):
        """GET /folders?parent_id=nonexistent → 404."""
        client, _ = authed_client
        resp = await client.get(
            f"/api/v1/dms/folders?parent_id={uuid.uuid4()}", headers=ORIGIN_HEADER
        )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_update_folder_not_found(self, authed_client):
        """PATCH /folders/{id} → 404 not found."""
        client, _ = authed_client
        resp = await client.patch(
            f"/api/v1/dms/folders/{uuid.uuid4()}",
            json={"name": "New"},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_update_folder_duplicate_name(self, authed_client):
        """PATCH /folders/{id} → 409 duplicate name."""
        client, _ = authed_client
        await client.post("/api/v1/dms/folders", json={"name": "A"}, headers=ORIGIN_HEADER)
        resp = await client.post("/api/v1/dms/folders", json={"name": "B"}, headers=ORIGIN_HEADER)
        b_id = resp.json()["id"]

        resp = await client.patch(
            f"/api/v1/dms/folders/{b_id}",
            json={"name": "A"},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 409

    @pytest.mark.asyncio
    async def test_update_folder_move_into_self(self, authed_client):
        """PATCH /folders/{id} → 400 cannot move into itself."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/folders", json={"name": "Self"}, headers=ORIGIN_HEADER
        )
        folder_id = resp.json()["id"]

        resp = await client.patch(
            f"/api/v1/dms/folders/{folder_id}",
            json={"parent_id": folder_id},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 400

    @pytest.mark.asyncio
    async def test_update_folder_move_parent_not_found(self, authed_client):
        """PATCH /folders/{id} → 404 parent not found."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/folders", json={"name": "Move"}, headers=ORIGIN_HEADER
        )
        folder_id = resp.json()["id"]

        resp = await client.patch(
            f"/api/v1/dms/folders/{folder_id}",
            json={"parent_id": str(uuid.uuid4())},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_update_folder_cycle_detection(self, authed_client):
        """PATCH /folders/{id} → 400 cannot move into descendant."""
        client, _ = authed_client
        resp = await client.post("/api/v1/dms/folders", json={"name": "P"}, headers=ORIGIN_HEADER)
        p_id = resp.json()["id"]
        resp = await client.post(
            "/api/v1/dms/folders",
            json={"name": "C", "parent_id": p_id},
            headers=ORIGIN_HEADER,
        )
        c_id = resp.json()["id"]

        # Try to move P into C (its own child)
        resp = await client.patch(
            f"/api/v1/dms/folders/{p_id}",
            json={"parent_id": c_id},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 400

    @pytest.mark.asyncio
    async def test_delete_folder_not_found(self, authed_client):
        """DELETE /folders/{id} → 404 not found."""
        client, _ = authed_client
        resp = await client.delete(f"/api/v1/dms/folders/{uuid.uuid4()}", headers=ORIGIN_HEADER)
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_delete_folder_invalid_id(self, authed_client):
        """DELETE /folders/{id} → 400 invalid id."""
        client, _ = authed_client
        resp = await client.delete("/api/v1/dms/folders/not-a-uuid", headers=ORIGIN_HEADER)
        assert resp.status_code == 400


class TestFileErrorPaths:
    """Error path tests for file endpoints."""

    @pytest.mark.asyncio
    async def test_upload_file_folder_not_found(self, authed_client):
        """POST /files/upload → 404 folder not found."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/files/upload",
            files={"file": ("test.pdf", PDF_CONTENT, "application/pdf")},
            data={"folder_id": str(uuid.uuid4())},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_get_file_not_found(self, authed_client):
        """GET /files/{id} → 404 not found."""
        client, _ = authed_client
        resp = await client.get(f"/api/v1/dms/files/{uuid.uuid4()}", headers=ORIGIN_HEADER)
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_get_file_invalid_id(self, authed_client):
        """GET /files/{id} → 400 invalid id."""
        client, _ = authed_client
        resp = await client.get("/api/v1/dms/files/bad-id", headers=ORIGIN_HEADER)
        assert resp.status_code == 400

    @pytest.mark.asyncio
    async def test_update_file_not_found(self, authed_client):
        """PATCH /files/{id} → 404 not found."""
        client, _ = authed_client
        resp = await client.patch(
            f"/api/v1/dms/files/{uuid.uuid4()}",
            json={"name": "new.pdf"},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_update_file_move_folder_not_found(self, authed_client):
        """PATCH /files/{id} → 404 folder not found."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/files/upload",
            files={"file": ("test.pdf", PDF_CONTENT, "application/pdf")},
            headers=ORIGIN_HEADER,
        )
        file_id = resp.json()["id"]

        resp = await client.patch(
            f"/api/v1/dms/files/{file_id}",
            json={"folder_id": str(uuid.uuid4())},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_delete_file_not_found(self, authed_client):
        """DELETE /files/{id} → 404 not found."""
        client, _ = authed_client
        resp = await client.delete(f"/api/v1/dms/files/{uuid.uuid4()}", headers=ORIGIN_HEADER)
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_restore_file_not_found(self, authed_client):
        """POST /files/{id}/restore → 404 not found."""
        client, _ = authed_client
        resp = await client.post(f"/api/v1/dms/files/{uuid.uuid4()}/restore", headers=ORIGIN_HEADER)
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_restore_file_not_deleted(self, authed_client):
        """POST /files/{id}/restore → 404 file not in trash."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/files/upload",
            files={"file": ("active.pdf", PDF_CONTENT, "application/pdf")},
            headers=ORIGIN_HEADER,
        )
        file_id = resp.json()["id"]

        resp = await client.post(f"/api/v1/dms/files/{file_id}/restore", headers=ORIGIN_HEADER)
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_preview_file_not_found(self, authed_client):
        """GET /files/{id}/preview → 404 not found."""
        client, _ = authed_client
        resp = await client.get(f"/api/v1/dms/files/{uuid.uuid4()}/preview", headers=ORIGIN_HEADER)
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_edit_session_not_found(self, authed_client):
        """POST /files/{id}/edit-session → 404 not found."""
        client, _ = authed_client
        resp = await client.post(
            f"/api/v1/dms/files/{uuid.uuid4()}/edit-session", headers=ORIGIN_HEADER
        )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_edit_session_xlsx(self, authed_client):
        """POST /files/{id}/edit-session → 200 for xlsx file."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/files/upload",
            files={
                "file": (
                    "sheet.xlsx",
                    b"PK\x03\x04",
                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                )
            },
            headers=ORIGIN_HEADER,
        )
        file_id = resp.json()["id"]

        resp = await client.post(f"/api/v1/dms/files/{file_id}/edit-session", headers=ORIGIN_HEADER)
        assert resp.status_code == 200
        assert resp.json()["document"]["fileType"] == "xlsx"

    @pytest.mark.asyncio
    async def test_edit_session_pptx(self, authed_client):
        """POST /files/{id}/edit-session → 200 for pptx file."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/files/upload",
            files={
                "file": (
                    "slides.pptx",
                    b"PK\x03\x04",
                    "application/vnd.openxmlformats-officedocument.presentationml.presentation",
                )
            },
            headers=ORIGIN_HEADER,
        )
        file_id = resp.json()["id"]

        resp = await client.post(f"/api/v1/dms/files/{file_id}/edit-session", headers=ORIGIN_HEADER)
        assert resp.status_code == 200
        assert resp.json()["document"]["fileType"] == "pptx"


class TestShareErrorPaths:
    """Error path tests for sharing endpoints."""

    @pytest.mark.asyncio
    async def test_share_file_not_found(self, authed_client):
        """POST /files/{id}/share → 404 file not found."""
        client, seed = authed_client
        viewer_id = str(seed["viewer_a"].id)
        resp = await client.post(
            f"/api/v1/dms/files/{uuid.uuid4()}/share",
            json={"user_ids": [viewer_id], "access_level": "read"},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_share_with_invalid_user_id(self, authed_client):
        """POST /files/{id}/share → 400 invalid user_id."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/files/upload",
            files={"file": ("share.pdf", PDF_CONTENT, "application/pdf")},
            headers=ORIGIN_HEADER,
        )
        file_id = resp.json()["id"]

        resp = await client.post(
            f"/api/v1/dms/files/{file_id}/share",
            json={"user_ids": ["not-a-uuid"], "access_level": "read"},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 400

    @pytest.mark.asyncio
    async def test_share_with_group(self, authed_client):
        """POST /files/{id}/share → 200 with group_ids."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/files/upload",
            files={"file": ("group.pdf", PDF_CONTENT, "application/pdf")},
            headers=ORIGIN_HEADER,
        )
        file_id = resp.json()["id"]

        group_id = str(uuid.uuid4())
        resp = await client.post(
            f"/api/v1/dms/files/{file_id}/share",
            json={"group_ids": [group_id], "access_level": "write"},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 200
        assert resp.json()["count"] == 1
        assert resp.json()["shared_with"][0]["group_id"] == group_id
        assert resp.json()["shared_with"][0]["access_level"] == "write"

    @pytest.mark.asyncio
    async def test_share_duplicate_ignored(self, authed_client):
        """POST /files/{id}/share → 200, duplicate shares ignored."""
        client, seed = authed_client
        resp = await client.post(
            "/api/v1/dms/files/upload",
            files={"file": ("dup.pdf", PDF_CONTENT, "application/pdf")},
            headers=ORIGIN_HEADER,
        )
        file_id = resp.json()["id"]
        viewer_id = str(seed["viewer_a"].id)

        # Share once
        await client.post(
            f"/api/v1/dms/files/{file_id}/share",
            json={"user_ids": [viewer_id], "access_level": "read"},
            headers=ORIGIN_HEADER,
        )
        # Share again — should be ignored
        resp = await client.post(
            f"/api/v1/dms/files/{file_id}/share",
            json={"user_ids": [viewer_id], "access_level": "read"},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 200
        assert resp.json()["count"] == 0

    @pytest.mark.asyncio
    async def test_remove_share_group(self, authed_client):
        """DELETE /files/{id}/share → 204, group share removed."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/files/upload",
            files={"file": ("grp.pdf", PDF_CONTENT, "application/pdf")},
            headers=ORIGIN_HEADER,
        )
        file_id = resp.json()["id"]
        group_id = str(uuid.uuid4())

        await client.post(
            f"/api/v1/dms/files/{file_id}/share",
            json={"group_ids": [group_id], "access_level": "read"},
            headers=ORIGIN_HEADER,
        )

        resp = await client.request(
            "DELETE",
            f"/api/v1/dms/files/{file_id}/share",
            json={"group_id": group_id},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 204

    @pytest.mark.asyncio
    async def test_remove_share_invalid_id(self, authed_client):
        """DELETE /files/{id}/share → 400 invalid user_id."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/files/upload",
            files={"file": ("bad.pdf", PDF_CONTENT, "application/pdf")},
            headers=ORIGIN_HEADER,
        )
        file_id = resp.json()["id"]

        resp = await client.request(
            "DELETE",
            f"/api/v1/dms/files/{file_id}/share",
            json={"user_id": "not-a-uuid"},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 400


class TestBulkErrorPaths:
    """Error path tests for bulk operations."""

    @pytest.mark.asyncio
    async def test_bulk_move_target_not_found(self, authed_client):
        """POST /files/bulk-move → 404 target folder not found."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/files/upload",
            files={"file": ("f.pdf", PDF_CONTENT, "application/pdf")},
            headers=ORIGIN_HEADER,
        )
        file_id = resp.json()["id"]

        resp = await client.post(
            "/api/v1/dms/files/bulk-move",
            json={"file_ids": [file_id], "target_folder_id": str(uuid.uuid4())},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 404

    @pytest.mark.asyncio
    async def test_bulk_move_to_root(self, authed_client):
        """POST /files/bulk-move → 200, move to root (null target)."""
        client, _ = authed_client
        # Create folder and upload file to it
        resp = await client.post("/api/v1/dms/folders", json={"name": "Src"}, headers=ORIGIN_HEADER)
        folder_id = resp.json()["id"]
        resp = await client.post(
            "/api/v1/dms/files/upload",
            files={"file": ("f.pdf", PDF_CONTENT, "application/pdf")},
            data={"folder_id": folder_id},
            headers=ORIGIN_HEADER,
        )
        file_id = resp.json()["id"]

        # Move to root (no target_folder_id)
        resp = await client.post(
            "/api/v1/dms/files/bulk-move",
            json={"file_ids": [file_id]},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 200
        assert resp.json()["target_folder_id"] is None

    @pytest.mark.asyncio
    async def test_bulk_move_invalid_file_id(self, authed_client):
        """POST /files/bulk-move → 400 invalid file_id."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/files/bulk-move",
            json={"file_ids": ["not-a-uuid"]},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 400

    @pytest.mark.asyncio
    async def test_bulk_delete_invalid_file_id(self, authed_client):
        """POST /files/bulk-delete → 400 invalid file_id."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/files/bulk-delete",
            json={"file_ids": ["not-a-uuid"]},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 400

    @pytest.mark.asyncio
    async def test_bulk_delete_nonexistent_files(self, authed_client):
        """POST /files/bulk-delete → 200, 0 deleted for nonexistent files."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/files/bulk-delete",
            json={"file_ids": [str(uuid.uuid4())]},
            headers=ORIGIN_HEADER,
        )
        assert resp.status_code == 200
        assert resp.json()["deleted"] == 0


class TestSearchEdgeCases:
    """Edge case tests for search endpoint."""

    @pytest.mark.asyncio
    async def test_search_no_results(self, authed_client):
        """GET /search?q=nonexistent → 200 + empty list."""
        client, _ = authed_client
        resp = await client.get("/api/v1/dms/search?q=nonexistent", headers=ORIGIN_HEADER)
        assert resp.status_code == 200
        assert resp.json() == []

    @pytest.mark.asyncio
    async def test_search_excludes_deleted(self, authed_client):
        """GET /search → excludes soft-deleted files."""
        client, _ = authed_client
        resp = await client.post(
            "/api/v1/dms/files/upload",
            files={"file": ("deleteme.pdf", PDF_CONTENT, "application/pdf")},
            headers=ORIGIN_HEADER,
        )
        file_id = resp.json()["id"]
        await client.delete(f"/api/v1/dms/files/{file_id}", headers=ORIGIN_HEADER)

        resp = await client.get("/api/v1/dms/search?q=deleteme", headers=ORIGIN_HEADER)
        assert resp.status_code == 200
        assert resp.json() == []


class TestSharedWithMeEdgeCases:
    """Edge case tests for shared-with-me endpoint."""

    @pytest.mark.asyncio
    async def test_shared_with_me_empty(self, authed_client):
        """GET /shared-with-me → 200 + empty list for admin (no shares)."""
        client, _ = authed_client
        resp = await client.get("/api/v1/dms/shared-with-me", headers=ORIGIN_HEADER)
        assert resp.status_code == 200
        assert resp.json() == []
