"""Tests for DELETE /api/v1/documents/{document_id} endpoint.

Uses FastAPI TestClient with mocked HMAC middleware, DB engine, Milvus client, and S3 client.
"""

import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
from fastapi.testclient import TestClient

# ---------------------------------------------------------------------------
# sys.path injection for airflow plugin imports
# ---------------------------------------------------------------------------
_plugins_dir = str(Path(__file__).resolve().parents[3] / "airflow" / "plugins")
if _plugins_dir not in sys.path:
    sys.path.insert(0, _plugins_dir)


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

_VALID_CTX = {"tenant_id": "t1", "node_id": "n1", "user_id": "u1", "role": "admin"}
_DOC_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"


class _FakeConnection:
    """Minimal fake connection for rls_connection context manager."""

    def __init__(self, doc_row=None):
        self._doc_row = doc_row
        self.executed = []

    def execute(self, stmt, params=None):
        sql = str(stmt)
        self.executed.append((sql, params))

        # SELECT document
        if "SELECT id, file_path FROM documents" in sql:
            result = MagicMock()
            result.fetchone.return_value = self._doc_row
            return result

        # DELETE
        result = MagicMock()
        result.rowcount = 1
        return result


def _make_doc_row(doc_id=_DOC_ID, file_path="t1/n1/doc/file.pdf"):
    row = MagicMock()
    row.__getitem__ = lambda self, idx: {0: doc_id, 1: file_path}[idx]
    return row


def _mock_rls(fake_conn):
    """Create a patched rls_connection that yields fake_conn."""
    mock = MagicMock()
    mock.return_value.__enter__ = MagicMock(return_value=fake_conn)
    mock.return_value.__exit__ = MagicMock(return_value=False)
    return mock


@pytest.fixture
def client():
    """TestClient with mocked HMAC middleware."""
    with patch("api.middleware.verify_hmac_headers", return_value=_VALID_CTX):
        from api.upload import upload_app
        yield TestClient(upload_app)


# ---------------------------------------------------------------------------
# Happy path
# ---------------------------------------------------------------------------


class TestDeleteDocument:

    def test_delete_success(self, client):
        """DELETE valid document — returns 200 with document_id."""
        fake_conn = _FakeConnection(doc_row=_make_doc_row())

        with patch("api.upload.rls_connection", _mock_rls(fake_conn)), \
             patch("api.upload.delete_chunks_by_document_id") as mock_milvus, \
             patch("api.upload._delete_s3_folder"):

            resp = client.delete(f"/{_DOC_ID}")

        assert resp.status_code == 200
        body = resp.json()
        assert body["document_id"] == _DOC_ID
        mock_milvus.assert_called_once_with(_DOC_ID, "t1", "n1")

    def test_delete_cleans_s3(self, client):
        """DELETE calls _delete_s3_folder with correct args."""
        fake_conn = _FakeConnection(doc_row=_make_doc_row())

        with patch("api.upload.rls_connection", _mock_rls(fake_conn)), \
             patch("api.upload.delete_chunks_by_document_id"), \
             patch("api.upload._delete_s3_folder") as mock_s3:

            resp = client.delete(f"/{_DOC_ID}")

        assert resp.status_code == 200
        mock_s3.assert_called_once_with("t1", "n1", _DOC_ID)


# ---------------------------------------------------------------------------
# Error cases
# ---------------------------------------------------------------------------


class TestDeleteErrors:

    def test_invalid_uuid_returns_422(self, client):
        resp = client.delete("/not-a-uuid")
        assert resp.status_code == 422
        assert resp.json()["error"]["code"] == "INVALID_ID"

    def test_not_found_returns_404(self, client):
        fake_conn = _FakeConnection(doc_row=None)

        with patch("api.upload.rls_connection", _mock_rls(fake_conn)):
            resp = client.delete(f"/{_DOC_ID}")

        assert resp.status_code == 404
        assert resp.json()["error"]["code"] == "NOT_FOUND"

    def test_milvus_failure_returns_500(self, client):
        """Milvus unavailable — returns 500, no PG/S3 changes."""
        fake_conn = _FakeConnection(doc_row=_make_doc_row())

        with patch("api.upload.rls_connection", _mock_rls(fake_conn)), \
             patch("api.upload.delete_chunks_by_document_id", side_effect=Exception("connection refused")):
            resp = client.delete(f"/{_DOC_ID}")

        assert resp.status_code == 500
        assert resp.json()["error"]["code"] == "MILVUS_ERROR"
        # Verify no DELETE was executed (only SELECT for existence check)
        delete_stmts = [s for s, _ in fake_conn.executed if "DELETE" in s]
        assert len(delete_stmts) == 0

    def test_s3_failure_still_returns_200(self, client):
        """S3 cleanup failure is best-effort — _delete_s3_folder handles errors internally."""
        fake_conn = _FakeConnection(doc_row=_make_doc_row())

        with patch("api.upload.rls_connection", _mock_rls(fake_conn)), \
             patch("api.upload.delete_chunks_by_document_id"), \
             patch("api.upload.get_s3_client") as mock_s3, \
             patch("api.upload.get_upload_bucket", return_value="test-bucket"):
            mock_s3.return_value.list_objects_v2.side_effect = Exception("s3 down")
            resp = client.delete(f"/{_DOC_ID}")

        assert resp.status_code == 200


# ---------------------------------------------------------------------------
# Auth
# ---------------------------------------------------------------------------


class TestDeleteAuth:

    def test_unauthorized_returns_401(self):
        from textiq_hmac import VerificationError
        with patch("api.middleware.verify_hmac_headers", side_effect=VerificationError("bad")):
            from api.upload import upload_app
            c = TestClient(upload_app)
            resp = c.delete(f"/{_DOC_ID}")
        assert resp.status_code == 401
