"""
Tests for Klausur API Routes.

Verifies:
- API endpoint behavior
- Request validation
- Response format
- Privacy guarantees at API level
"""
import pytest
from unittest.mock import MagicMock, patch, AsyncMock
from fastapi.testclient import TestClient
from fastapi import FastAPI

from klausur.routes import router
from klausur.db_models import SessionStatus, DocumentStatus


@pytest.fixture
def app():
    """Create test FastAPI app."""
    app = FastAPI()
    app.include_router(router, prefix="/api")
    return app


@pytest.fixture
def client(app):
    """Create test client."""
    return TestClient(app)


class TestSessionEndpoints:
    """Tests for session-related endpoints."""

    @patch('klausur.routes.KlausurRepository')
    @patch('klausur.routes.get_db')
    def test_create_session_returns_201(self, mock_get_db, mock_repo_class, client):
        """Creating a session should return 201."""
        # Setup mocks
        mock_db = MagicMock()
        mock_get_db.return_value = iter([mock_db])

        mock_repo = MagicMock()
        mock_repo_class.return_value = mock_repo

        mock_session = MagicMock()
        mock_session.id = "session-123"
        mock_session.name = "Test Klausur"
        mock_session.subject = "Mathe"
        mock_session.class_name = "10a"
        mock_session.total_points = 100
        mock_session.status = SessionStatus.CREATED
        mock_session.document_count = 0
        mock_session.processed_count = 0
        mock_session.created_at = "2024-01-15T10:00:00"
        mock_session.completed_at = None
        mock_session.retention_until = "2024-02-15T10:00:00"

        mock_repo.create_session.return_value = mock_session

        response = client.post("/api/klausur/sessions", json={
            "name": "Test Klausur",
            "subject": "Mathe",
            "class_name": "10a"
        })

        assert response.status_code == 201
        data = response.json()
        assert data["name"] == "Test Klausur"
        assert data["status"] == "created"

    @patch('klausur.routes.KlausurRepository')
    @patch('klausur.routes.get_db')
    def test_create_session_validates_name(self, mock_get_db, mock_repo_class, client):
        """Session name is required and must not be empty."""
        response = client.post("/api/klausur/sessions", json={
            "name": "",  # Empty name
            "subject": "Mathe"
        })

        assert response.status_code == 422  # Validation error

    @patch('klausur.routes.KlausurRepository')
    @patch('klausur.routes.get_db')
    def test_list_sessions_returns_array(self, mock_get_db, mock_repo_class, client):
        """Listing sessions should return an array."""
        mock_db = MagicMock()
        mock_get_db.return_value = iter([mock_db])

        mock_repo = MagicMock()
        mock_repo_class.return_value = mock_repo
        mock_repo.list_sessions.return_value = []

        response = client.get("/api/klausur/sessions")

        assert response.status_code == 200
        data = response.json()
        assert "sessions" in data
        assert isinstance(data["sessions"], list)

    @patch('klausur.routes.KlausurRepository')
    @patch('klausur.routes.get_db')
    def test_get_session_404_when_not_found(self, mock_get_db, mock_repo_class, client):
        """Getting non-existent session should return 404."""
        mock_db = MagicMock()
        mock_get_db.return_value = iter([mock_db])

        mock_repo = MagicMock()
        mock_repo_class.return_value = mock_repo
        mock_repo.get_session.return_value = None

        response = client.get("/api/klausur/sessions/nonexistent-123")

        assert response.status_code == 404


class TestQREndpoints:
    """Tests for QR code generation endpoints."""

    @patch('klausur.routes.KlausurRepository')
    @patch('klausur.routes.get_pseudonymizer')
    @patch('klausur.routes.get_db')
    def test_generate_qr_batch_creates_tokens(
        self, mock_get_db, mock_get_pseudonymizer, mock_repo_class, client
    ):
        """QR batch generation should create correct number of tokens."""
        mock_db = MagicMock()
        mock_get_db.return_value = iter([mock_db])

        mock_repo = MagicMock()
        mock_repo_class.return_value = mock_repo

        mock_session = MagicMock()
        mock_repo.get_session.return_value = mock_session

        mock_batch = MagicMock()
        mock_batch.id = "batch-123"
        mock_batch.student_count = 5
        mock_repo.create_qr_batch.return_value = mock_batch

        mock_pseudonymizer = MagicMock()
        mock_pseudonymizer.generate_batch_tokens.return_value = [
            "token-1", "token-2", "token-3", "token-4", "token-5"
        ]
        mock_get_pseudonymizer.return_value = mock_pseudonymizer

        response = client.post("/api/klausur/sessions/session-123/qr-batch", json={
            "student_count": 5
        })

        assert response.status_code == 200
        data = response.json()
        assert len(data["generated_tokens"]) == 5

    @patch('klausur.routes.KlausurRepository')
    @patch('klausur.routes.get_db')
    def test_qr_batch_validates_student_count(self, mock_get_db, mock_repo_class, client):
        """Student count must be within valid range."""
        # Too many students
        response = client.post("/api/klausur/sessions/session-123/qr-batch", json={
            "student_count": 200  # Max is 100
        })

        assert response.status_code == 422


class TestUploadEndpoints:
    """Tests for document upload endpoints."""

    @patch('klausur.routes.KlausurRepository')
    @patch('klausur.routes.get_pseudonymizer')
    @patch('klausur.routes.get_db')
    def test_upload_applies_redaction_by_default(
        self, mock_get_db, mock_get_pseudonymizer, mock_repo_class, client
    ):
        """Upload should apply header redaction by default."""
        mock_db = MagicMock()
        mock_get_db.return_value = iter([mock_db])

        mock_repo = MagicMock()
        mock_repo_class.return_value = mock_repo

        mock_session = MagicMock()
        mock_repo.get_session.return_value = mock_session

        mock_doc = MagicMock()
        mock_doc.doc_token = "doc-token-123"
        mock_doc.session_id = "session-123"
        mock_doc.status = DocumentStatus.UPLOADED
        mock_doc.page_number = 1
        mock_doc.total_pages = 1
        mock_doc.ocr_confidence = 0
        mock_doc.ai_score = None
        mock_doc.ai_grade = None
        mock_doc.ai_feedback = None
        mock_doc.created_at = "2024-01-15T10:00:00"
        mock_doc.processing_completed_at = None
        mock_repo.create_document.return_value = mock_doc

        mock_pseudonymizer = MagicMock()
        mock_pseudonymizer.detect_qr_code.return_value = MagicMock(doc_token=None)
        mock_pseudonymizer.generate_doc_token.return_value = "doc-token-123"
        mock_pseudonymizer.smart_redact_header.return_value = MagicMock(
            redaction_applied=True,
            redacted_image=b"redacted",
            redacted_height=300
        )
        mock_get_pseudonymizer.return_value = mock_pseudonymizer

        # Create a minimal file upload
        response = client.post(
            "/api/klausur/sessions/session-123/upload",
            files={"file": ("test.png", b"fake image data", "image/png")}
        )

        # Verify redaction was called
        mock_pseudonymizer.smart_redact_header.assert_called_once()


class TestResultsEndpoints:
    """Tests for results endpoints."""

    @patch('klausur.routes.KlausurRepository')
    @patch('klausur.routes.get_db')
    def test_results_only_return_pseudonymized_data(
        self, mock_get_db, mock_repo_class, client
    ):
        """Results should only contain doc_tokens, not names."""
        mock_db = MagicMock()
        mock_get_db.return_value = iter([mock_db])

        mock_repo = MagicMock()
        mock_repo_class.return_value = mock_repo

        mock_session = MagicMock()
        mock_session.total_points = 100
        mock_repo.get_session.return_value = mock_session

        mock_doc = MagicMock()
        mock_doc.doc_token = "anonymous-token-123"
        mock_doc.status = DocumentStatus.COMPLETED
        mock_doc.ai_score = 85
        mock_doc.ai_grade = "2+"
        mock_doc.ai_feedback = "Good work"
        mock_doc.ai_details = {}
        mock_repo.list_documents.return_value = [mock_doc]

        response = client.get("/api/klausur/sessions/session-123/results")

        assert response.status_code == 200
        data = response.json()

        # Results should use doc_token, not student name
        assert len(data) == 1
        assert "doc_token" in data[0]
        assert "student_name" not in data[0]
        assert "name" not in data[0]


class TestIdentityMapEndpoints:
    """Tests for identity map (vault) endpoints."""

    @patch('klausur.routes.KlausurRepository')
    @patch('klausur.routes.get_db')
    def test_store_identity_map_accepts_encrypted_data(
        self, mock_get_db, mock_repo_class, client
    ):
        """Identity map endpoint should accept encrypted data."""
        mock_db = MagicMock()
        mock_get_db.return_value = iter([mock_db])

        mock_repo = MagicMock()
        mock_repo_class.return_value = mock_repo

        mock_session = MagicMock()
        mock_repo.update_session_identity_map.return_value = mock_session

        # Base64 encoded "encrypted" data
        import base64
        encrypted = base64.b64encode(b"encrypted identity map").decode()

        response = client.post("/api/klausur/sessions/session-123/identity-map", json={
            "encrypted_data": encrypted,
            "iv": "base64iv=="
        })

        assert response.status_code == 204

    @patch('klausur.routes.KlausurRepository')
    @patch('klausur.routes.get_db')
    def test_get_identity_map_returns_encrypted_blob(
        self, mock_get_db, mock_repo_class, client
    ):
        """Getting identity map should return encrypted blob."""
        mock_db = MagicMock()
        mock_get_db.return_value = iter([mock_db])

        mock_repo = MagicMock()
        mock_repo_class.return_value = mock_repo

        mock_session = MagicMock()
        mock_session.encrypted_identity_map = b"encrypted data"
        mock_session.identity_map_iv = "ivvalue"
        mock_repo.get_session.return_value = mock_session

        response = client.get("/api/klausur/sessions/session-123/identity-map")

        assert response.status_code == 200
        data = response.json()
        assert "encrypted_data" in data
        assert "iv" in data


class TestPrivacyAtAPILevel:
    """Tests verifying privacy guarantees at API level."""

    def test_no_student_names_in_any_response_schema(self):
        """Verify response schemas don't include student names."""
        from klausur.routes import (
            SessionResponse, DocumentResponse, CorrectionResultResponse
        )

        # Check all response model fields
        session_fields = SessionResponse.model_fields.keys()
        doc_fields = DocumentResponse.model_fields.keys()
        result_fields = CorrectionResultResponse.model_fields.keys()

        all_fields = list(session_fields) + list(doc_fields) + list(result_fields)

        # Should not contain student-name-related fields
        # Note: "name" alone is allowed (e.g., session/exam name like "Mathe Klausur")
        forbidden = ["student_name", "schueler_name", "student", "pupil", "schueler"]
        for field in all_fields:
            assert field.lower() not in forbidden, f"Field '{field}' may contain PII"

    def test_identity_map_request_requires_encryption(self):
        """Identity map must be encrypted before storage."""
        from klausur.routes import IdentityMapUpdate

        # Check that schema requires encrypted_data, not plain names
        fields = IdentityMapUpdate.model_fields.keys()

        assert "encrypted_data" in fields
        assert "names" not in fields
        assert "student_names" not in fields
