"""Tests for the FastAPI server."""

from __future__ import annotations

import importlib
from unittest.mock import MagicMock, patch

import pytest
from fastapi.testclient import TestClient

from clawguard.redactor import RedactResult


@pytest.fixture
def client(monkeypatch):
    monkeypatch.setenv("CLAWGUARD_TOKEN", "test-token")

    mock_redactor = MagicMock()
    mock_redactor.redact.return_value = RedactResult(
        redacted_text="Contact [EMAIL]",
        redacted_count=1,
        redacted_items=[{
            "original": "john@example.com",
            "label": "email",
            "replacement": "[EMAIL]",
            "confidence": 0.99,
        }],
    )

    with patch("clawguard.server.PIIRedactor", return_value=mock_redactor):
        import clawguard.server as srv
        importlib.reload(srv)
        with TestClient(srv.app) as tc:
            yield tc


@pytest.fixture
def clean_client(monkeypatch):
    """Client whose mock redactor returns no PII."""
    monkeypatch.setenv("CLAWGUARD_TOKEN", "test-token")

    mock_redactor = MagicMock()
    mock_redactor.redact.return_value = RedactResult(
        redacted_text="Hello world",
        redacted_count=0,
        redacted_items=[],
    )

    with patch("clawguard.server.PIIRedactor", return_value=mock_redactor):
        import clawguard.server as srv
        importlib.reload(srv)
        with TestClient(srv.app) as tc:
            yield tc


class TestHealth:
    def test_health_returns_ok(self, client):
        resp = client.get("/health")
        assert resp.status_code == 200
        assert resp.json()["status"] == "ok"

    def test_health_requires_no_auth(self, client):
        resp = client.get("/health")
        assert resp.status_code == 200


class TestRedactEndpoint:
    def test_valid_request_returns_200(self, client):
        resp = client.post(
            "/redact",
            json={"text": "Contact john@example.com"},
            headers={"Authorization": "Bearer test-token"},
        )
        assert resp.status_code == 200

    def test_response_shape(self, client):
        resp = client.post(
            "/redact",
            json={"text": "Contact john@example.com"},
            headers={"Authorization": "Bearer test-token"},
        )
        data = resp.json()
        assert "redacted_text" in data
        assert "redacted_count" in data
        assert "redacted_items" in data

    def test_redacted_text_returned(self, client):
        resp = client.post(
            "/redact",
            json={"text": "Contact john@example.com"},
            headers={"Authorization": "Bearer test-token"},
        )
        assert resp.json()["redacted_text"] == "Contact [EMAIL]"

    def test_redacted_count_returned(self, client):
        resp = client.post(
            "/redact",
            json={"text": "Contact john@example.com"},
            headers={"Authorization": "Bearer test-token"},
        )
        assert resp.json()["redacted_count"] == 1

    def test_empty_text_short_circuits(self, clean_client):
        resp = clean_client.post(
            "/redact",
            json={"text": ""},
            headers={"Authorization": "Bearer test-token"},
        )
        assert resp.status_code == 200
        data = resp.json()
        assert data["redacted_text"] == ""
        assert data["redacted_count"] == 0
        assert data["redacted_items"] == []

    def test_oversized_text_rejected(self, monkeypatch):
        monkeypatch.setenv("CLAWGUARD_TOKEN", "test-token")
        monkeypatch.setenv("MAX_TEXT_LENGTH", "10")

        mock_redactor = MagicMock()
        mock_redactor.redact.return_value = RedactResult("", 0, [])

        with patch("clawguard.server.PIIRedactor", return_value=mock_redactor):
            import clawguard.server as srv
            importlib.reload(srv)
            with TestClient(srv.app) as tc:
                resp = tc.post(
                    "/redact",
                    json={"text": "x" * 11},
                    headers={"Authorization": "Bearer test-token"},
                )
        assert resp.status_code == 413


class TestOriginalField:
    def test_original_omitted_by_default(self, client):
        resp = client.post(
            "/redact",
            json={"text": "Contact john@example.com"},
            headers={"Authorization": "Bearer test-token"},
        )
        items = resp.json()["redacted_items"]
        assert len(items) == 1
        assert items[0].get("original") is None

    def test_original_included_when_requested(self, client):
        resp = client.post(
            "/redact?include_original=true",
            json={"text": "Contact john@example.com"},
            headers={"Authorization": "Bearer test-token"},
        )
        items = resp.json()["redacted_items"]
        assert len(items) == 1
        assert items[0]["original"] == "john@example.com"


class TestAuth:
    def test_missing_auth_rejected(self, client):
        resp = client.post("/redact", json={"text": "hello"})
        assert resp.status_code in (401, 403)

    def test_wrong_token_rejected(self, client):
        resp = client.post(
            "/redact",
            json={"text": "hello"},
            headers={"Authorization": "Bearer wrong-token"},
        )
        assert resp.status_code == 401

    def test_correct_token_accepted(self, client):
        resp = client.post(
            "/redact",
            json={"text": "hello"},
            headers={"Authorization": "Bearer test-token"},
        )
        assert resp.status_code == 200


class TestStartupGuard:
    def test_missing_token_raises_on_import(self, monkeypatch):
        monkeypatch.delenv("CLAWGUARD_TOKEN", raising=False)
        import clawguard.server as srv
        with pytest.raises(RuntimeError, match="CLAWGUARD_TOKEN"):
            importlib.reload(srv)

    def test_default_placeholder_raises_on_import(self, monkeypatch):
        monkeypatch.setenv("CLAWGUARD_TOKEN", "change-me")
        import clawguard.server as srv
        with pytest.raises(RuntimeError, match="CLAWGUARD_TOKEN"):
            importlib.reload(srv)


class TestValidation:
    def test_missing_text_field_rejected(self, client):
        resp = client.post(
            "/redact",
            json={},
            headers={"Authorization": "Bearer test-token"},
        )
        assert resp.status_code == 422

    def test_empty_text_accepted(self, client):
        resp = client.post(
            "/redact",
            json={"text": ""},
            headers={"Authorization": "Bearer test-token"},
        )
        assert resp.status_code == 200
