"""
UI Test API

Provides endpoints for the interactive UI Test Wizard.
Allows testing of middleware components with educational feedback.
"""

from __future__ import annotations

import asyncio
import time
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Dict, List, Optional

import httpx
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel


# ==============================================
# Data Models
# ==============================================


class TestResult(BaseModel):
    """Result of a single test."""

    name: str
    description: str
    expected: str
    actual: str
    status: str  # "passed" | "failed" | "pending" | "skipped"
    duration_ms: float = 0.0
    error_message: Optional[str] = None


class TestCategoryResult(BaseModel):
    """Result of a test category."""

    category: str
    display_name: str
    description: str
    why_important: str
    tests: List[TestResult]
    passed: int
    failed: int
    total: int
    duration_ms: float


class FullTestResults(BaseModel):
    """Full test results for all categories."""

    timestamp: str
    categories: List[TestCategoryResult]
    total_passed: int
    total_failed: int
    total_tests: int
    duration_ms: float


# ==============================================
# Educational Content
# ==============================================

EDUCATION_CONTENT = {
    "request-id": {
        "display_name": "Request-ID & Tracing",
        "description": "Request-ID Middleware fuer Distributed Tracing",
        "why_important": """
Stellen Sie sich vor, ein Benutzer meldet einen Fehler. Ohne Request-ID muessen
Sie Tausende von Log-Eintraegen durchsuchen. Mit Request-ID finden Sie den genauen
Pfad der Anfrage durch alle Microservices in Sekunden.

Request-IDs sind essentiell fuer:
- Fehlersuche in verteilten Systemen
- Performance-Analyse
- Audit-Trails fuer Compliance
""",
    },
    "security-headers": {
        "display_name": "Security Headers",
        "description": "HTTP Security Headers zum Schutz vor Web-Angriffen",
        "why_important": """
Security Headers sind Anweisungen an den Browser, wie er Ihre Seite schuetzen soll:

- X-Content-Type-Options: nosniff - Verhindert MIME-Sniffing Angriffe
- X-Frame-Options: DENY - Blockiert Clickjacking-Angriffe
- Content-Security-Policy - Stoppt XSS durch Whitelist erlaubter Quellen
- Strict-Transport-Security - Erzwingt HTTPS

OWASP empfiehlt diese Headers als Mindeststandard. Sie sind Pflicht fuer
DSGVO-Konformitaet und schuetzen Ihre Benutzer vor gaengigen Angriffen.
""",
    },
    "rate-limiter": {
        "display_name": "Rate Limiting",
        "description": "Schutz vor Brute-Force und DDoS Angriffen",
        "why_important": """
Ohne Rate Limiting kann ein Angreifer:
- Passwort-Brute-Force durchfuehren (Millionen Versuche/Minute)
- Ihre Server mit Anfragen ueberfluten (DDoS)
- Teure API-Aufrufe missbrauchen

BreakPilot limitiert:
- 100 Anfragen/Minute pro IP (allgemein)
- 20 Anfragen/Minute fuer Auth-Endpoints
- 500 Anfragen/Minute pro authentifiziertem Benutzer
""",
    },
    "pii-redactor": {
        "display_name": "PII Redaktion",
        "description": "Automatische Entfernung personenbezogener Daten aus Logs",
        "why_important": """
Personenbezogene Daten in Logs sind ein DSGVO-Verstoss:

- Email-Adressen: Bussgelder bis 20 Mio. EUR
- IP-Adressen: Gelten als personenbezogen (EuGH-Urteil)
- Telefonnummern: Direkter Personenbezug

Der PII Redactor erkennt automatisch:
- Email-Adressen → [EMAIL_REDACTED]
- IP-Adressen → [IP_REDACTED]
- Deutsche Telefonnummern → [PHONE_REDACTED]
- IBAN-Nummern → [IBAN_REDACTED]
""",
    },
    "input-gate": {
        "display_name": "Input Validierung",
        "description": "Validierung eingehender Anfragen und Uploads",
        "why_important": """
Das Input Gate prueft jede Anfrage bevor sie Ihren Code erreicht:

- Groessenlimit: Blockiert ueberdimensionierte Payloads (DoS-Schutz)
- Content-Type: Erlaubt nur erwartete Formate
- Dateiendungen: Blockiert .exe, .bat, .sh Uploads

Ein Angreifer, der an Ihrem Code vorbeikommt, wird hier gestoppt.
Dies ist Ihre erste Verteidigungslinie gegen Injection-Angriffe.
""",
    },
    "cors": {
        "display_name": "CORS",
        "description": "Cross-Origin Resource Sharing Konfiguration",
        "why_important": """
CORS bestimmt, welche Websites Ihre API aufrufen duerfen:

- Zu offen (*): Jede Website kann Ihre API missbrauchen
- Zu streng: Ihre eigene Frontend-App wird blockiert

BreakPilot erlaubt nur:
- https://breakpilot.app (Produktion)
- http://localhost:3000 (Development)

Falsch konfiguriertes CORS ist eine der haeufigsten Sicherheitsluecken
in Web-Anwendungen.
""",
    },
}


# ==============================================
# Test Runner
# ==============================================


class MiddlewareTestRunner:
    """Runs middleware tests against the backend."""

    def __init__(self, base_url: str = "http://localhost:8000"):
        self.base_url = base_url

    async def test_request_id(self) -> TestCategoryResult:
        """Test Request-ID middleware."""
        tests: List[TestResult] = []
        start_time = time.time()

        async with httpx.AsyncClient() as client:
            # Test 1: New request should get a Request-ID
            test_start = time.time()
            try:
                response = await client.get(f"{self.base_url}/api/health")
                request_id = response.headers.get("X-Request-ID", "")

                tests.append(
                    TestResult(
                        name="Generiert neue Request-ID",
                        description="Eine neue UUID wird generiert wenn keine vorhanden ist",
                        expected="UUID im X-Request-ID Header",
                        actual=request_id[:36] if len(request_id) >= 36 else request_id,
                        status="passed" if len(request_id) >= 36 else "failed",
                        duration_ms=(time.time() - test_start) * 1000,
                    )
                )
            except Exception as e:
                tests.append(
                    TestResult(
                        name="Generiert neue Request-ID",
                        description="Eine neue UUID wird generiert wenn keine vorhanden ist",
                        expected="UUID im X-Request-ID Header",
                        actual="Fehler",
                        status="failed",
                        duration_ms=(time.time() - test_start) * 1000,
                        error_message=str(e),
                    )
                )

            # Test 2: Existing Request-ID should be propagated
            test_start = time.time()
            custom_id = "test-request-id-12345"
            try:
                response = await client.get(
                    f"{self.base_url}/api/health",
                    headers={"X-Request-ID": custom_id},
                )
                returned_id = response.headers.get("X-Request-ID", "")

                tests.append(
                    TestResult(
                        name="Propagiert vorhandene Request-ID",
                        description="Existierende X-Request-ID wird weitergeleitet",
                        expected=custom_id,
                        actual=returned_id,
                        status="passed" if returned_id == custom_id else "failed",
                        duration_ms=(time.time() - test_start) * 1000,
                    )
                )
            except Exception as e:
                tests.append(
                    TestResult(
                        name="Propagiert vorhandene Request-ID",
                        description="Existierende X-Request-ID wird weitergeleitet",
                        expected=custom_id,
                        actual="Fehler",
                        status="failed",
                        duration_ms=(time.time() - test_start) * 1000,
                        error_message=str(e),
                    )
                )

            # Test 3: Correlation-ID should work
            test_start = time.time()
            correlation_id = "correlation-12345"
            try:
                response = await client.get(
                    f"{self.base_url}/api/health",
                    headers={"X-Correlation-ID": correlation_id},
                )
                returned_id = response.headers.get("X-Request-ID", "")

                tests.append(
                    TestResult(
                        name="Unterstuetzt X-Correlation-ID",
                        description="X-Correlation-ID wird als Request-ID verwendet",
                        expected=correlation_id,
                        actual=returned_id,
                        status="passed" if returned_id == correlation_id else "failed",
                        duration_ms=(time.time() - test_start) * 1000,
                    )
                )
            except Exception as e:
                tests.append(
                    TestResult(
                        name="Unterstuetzt X-Correlation-ID",
                        description="X-Correlation-ID wird als Request-ID verwendet",
                        expected=correlation_id,
                        actual="Fehler",
                        status="failed",
                        duration_ms=(time.time() - test_start) * 1000,
                        error_message=str(e),
                    )
                )

        passed = sum(1 for t in tests if t.status == "passed")
        return TestCategoryResult(
            category="request-id",
            display_name=EDUCATION_CONTENT["request-id"]["display_name"],
            description=EDUCATION_CONTENT["request-id"]["description"],
            why_important=EDUCATION_CONTENT["request-id"]["why_important"],
            tests=tests,
            passed=passed,
            failed=len(tests) - passed,
            total=len(tests),
            duration_ms=(time.time() - start_time) * 1000,
        )

    async def test_security_headers(self) -> TestCategoryResult:
        """Test Security Headers middleware."""
        tests: List[TestResult] = []
        start_time = time.time()

        async with httpx.AsyncClient() as client:
            try:
                response = await client.get(f"{self.base_url}/api/health")
                headers = response.headers

                # Required headers
                header_tests = [
                    ("X-Content-Type-Options", "nosniff", "Verhindert MIME-Sniffing"),
                    ("X-Frame-Options", "DENY", "Verhindert Clickjacking"),
                    ("X-XSS-Protection", "1; mode=block", "XSS Filter aktiviert"),
                    ("Referrer-Policy", None, "Kontrolliert Referrer-Informationen"),
                ]

                for header_name, expected_value, description in header_tests:
                    test_start = time.time()
                    actual = headers.get(header_name, "")

                    if expected_value:
                        status = "passed" if actual == expected_value else "failed"
                    else:
                        status = "passed" if actual else "failed"

                    tests.append(
                        TestResult(
                            name=f"{header_name} Header",
                            description=description,
                            expected=expected_value or "Beliebiger Wert",
                            actual=actual or "(nicht gesetzt)",
                            status=status,
                            duration_ms=(time.time() - test_start) * 1000,
                        )
                    )

            except Exception as e:
                tests.append(
                    TestResult(
                        name="Security Headers",
                        description="Ueberpruefen der Security Headers",
                        expected="Headers vorhanden",
                        actual="Fehler",
                        status="failed",
                        error_message=str(e),
                    )
                )

        passed = sum(1 for t in tests if t.status == "passed")
        return TestCategoryResult(
            category="security-headers",
            display_name=EDUCATION_CONTENT["security-headers"]["display_name"],
            description=EDUCATION_CONTENT["security-headers"]["description"],
            why_important=EDUCATION_CONTENT["security-headers"]["why_important"],
            tests=tests,
            passed=passed,
            failed=len(tests) - passed,
            total=len(tests),
            duration_ms=(time.time() - start_time) * 1000,
        )

    async def test_pii_redactor(self) -> TestCategoryResult:
        """Test PII Redactor."""
        tests: List[TestResult] = []
        start_time = time.time()

        try:
            from middleware.pii_redactor import PIIRedactor, redact_pii

            redactor = PIIRedactor()

            # Test cases
            test_cases = [
                (
                    "Email Redaktion",
                    "User test@example.com hat sich angemeldet",
                    "test@example.com",
                    "[EMAIL_REDACTED]",
                ),
                (
                    "IPv4 Redaktion",
                    "Request von 192.168.1.100",
                    "192.168.1.100",
                    "[IP_REDACTED]",
                ),
                (
                    "Telefon Redaktion",
                    "Anruf von +49 30 12345678",
                    "+49 30 12345678",
                    "[PHONE_REDACTED]",
                ),
            ]

            for name, text, pii, expected_replacement in test_cases:
                test_start = time.time()
                result = redact_pii(text)
                status = "passed" if pii not in result and expected_replacement in result else "failed"

                tests.append(
                    TestResult(
                        name=name,
                        description=f"Prueft ob {pii} korrekt entfernt wird",
                        expected=expected_replacement,
                        actual=result.replace(text.replace(pii, ""), "").strip(),
                        status=status,
                        duration_ms=(time.time() - test_start) * 1000,
                    )
                )

            # Test non-PII preservation
            test_start = time.time()
            clean_text = "Normale Nachricht ohne PII"
            result = redact_pii(clean_text)
            tests.append(
                TestResult(
                    name="Nicht-PII bleibt erhalten",
                    description="Text ohne PII wird nicht veraendert",
                    expected=clean_text,
                    actual=result,
                    status="passed" if result == clean_text else "failed",
                    duration_ms=(time.time() - test_start) * 1000,
                )
            )

        except Exception as e:
            tests.append(
                TestResult(
                    name="PII Redactor",
                    description="PII Redaction testen",
                    expected="Redaktion funktioniert",
                    actual="Fehler",
                    status="failed",
                    error_message=str(e),
                )
            )

        passed = sum(1 for t in tests if t.status == "passed")
        return TestCategoryResult(
            category="pii-redactor",
            display_name=EDUCATION_CONTENT["pii-redactor"]["display_name"],
            description=EDUCATION_CONTENT["pii-redactor"]["description"],
            why_important=EDUCATION_CONTENT["pii-redactor"]["why_important"],
            tests=tests,
            passed=passed,
            failed=len(tests) - passed,
            total=len(tests),
            duration_ms=(time.time() - start_time) * 1000,
        )

    async def test_input_gate(self) -> TestCategoryResult:
        """Test Input Gate middleware."""
        tests: List[TestResult] = []
        start_time = time.time()

        try:
            from middleware.input_gate import validate_file_upload, InputGateConfig

            config = InputGateConfig()

            # Test blocked extensions
            blocked_files = [
                ("malware.exe", True),
                ("script.bat", True),
                ("document.pdf", False),
                ("image.jpg", False),
            ]

            for filename, should_block in blocked_files:
                test_start = time.time()
                content_type = "application/pdf" if filename.endswith(".pdf") else "image/jpeg" if filename.endswith(".jpg") else "application/octet-stream"
                valid, _ = validate_file_upload(filename, content_type, 1000, config)

                if should_block:
                    status = "passed" if not valid else "failed"
                    expected = "Blockiert"
                    actual = "Blockiert" if not valid else "Erlaubt"
                else:
                    status = "passed" if valid else "failed"
                    expected = "Erlaubt"
                    actual = "Erlaubt" if valid else "Blockiert"

                tests.append(
                    TestResult(
                        name=f"Datei: {filename}",
                        description=f"Prueft ob {filename} korrekt behandelt wird",
                        expected=expected,
                        actual=actual,
                        status=status,
                        duration_ms=(time.time() - test_start) * 1000,
                    )
                )

        except Exception as e:
            tests.append(
                TestResult(
                    name="Input Gate",
                    description="Input Validierung testen",
                    expected="Validierung funktioniert",
                    actual="Fehler",
                    status="failed",
                    error_message=str(e),
                )
            )

        passed = sum(1 for t in tests if t.status == "passed")
        return TestCategoryResult(
            category="input-gate",
            display_name=EDUCATION_CONTENT["input-gate"]["display_name"],
            description=EDUCATION_CONTENT["input-gate"]["description"],
            why_important=EDUCATION_CONTENT["input-gate"]["why_important"],
            tests=tests,
            passed=passed,
            failed=len(tests) - passed,
            total=len(tests),
            duration_ms=(time.time() - start_time) * 1000,
        )

    async def test_rate_limiter(self) -> TestCategoryResult:
        """Test Rate Limiter middleware."""
        tests: List[TestResult] = []
        start_time = time.time()

        async with httpx.AsyncClient() as client:
            # Test 1: Rate limit headers present
            test_start = time.time()
            try:
                response = await client.get(f"{self.base_url}/api/health")
                has_headers = (
                    "X-RateLimit-Limit" in response.headers
                    or "X-RateLimit-Remaining" in response.headers
                )

                tests.append(
                    TestResult(
                        name="Rate Limit Headers",
                        description="Prueft ob Rate Limit Headers vorhanden sind",
                        expected="X-RateLimit-* Headers",
                        actual="Vorhanden" if has_headers else "Fehlen",
                        status="passed" if has_headers else "skipped",
                        duration_ms=(time.time() - test_start) * 1000,
                    )
                )
            except Exception as e:
                tests.append(
                    TestResult(
                        name="Rate Limit Headers",
                        description="Prueft ob Rate Limit Headers vorhanden sind",
                        expected="X-RateLimit-* Headers",
                        actual="Fehler",
                        status="failed",
                        duration_ms=(time.time() - test_start) * 1000,
                        error_message=str(e),
                    )
                )

            # Test 2: Normal requests pass
            test_start = time.time()
            try:
                response = await client.get(f"{self.base_url}/api/health")
                tests.append(
                    TestResult(
                        name="Normale Anfragen erlaubt",
                        description="Einzelne Anfragen werden durchgelassen",
                        expected="Status 200",
                        actual=f"Status {response.status_code}",
                        status="passed" if response.status_code == 200 else "failed",
                        duration_ms=(time.time() - test_start) * 1000,
                    )
                )
            except Exception as e:
                tests.append(
                    TestResult(
                        name="Normale Anfragen erlaubt",
                        description="Einzelne Anfragen werden durchgelassen",
                        expected="Status 200",
                        actual="Fehler",
                        status="failed",
                        duration_ms=(time.time() - test_start) * 1000,
                        error_message=str(e),
                    )
                )

        passed = sum(1 for t in tests if t.status == "passed")
        return TestCategoryResult(
            category="rate-limiter",
            display_name=EDUCATION_CONTENT["rate-limiter"]["display_name"],
            description=EDUCATION_CONTENT["rate-limiter"]["description"],
            why_important=EDUCATION_CONTENT["rate-limiter"]["why_important"],
            tests=tests,
            passed=passed,
            failed=len(tests) - passed,
            total=len(tests),
            duration_ms=(time.time() - start_time) * 1000,
        )

    async def test_cors(self) -> TestCategoryResult:
        """Test CORS middleware."""
        tests: List[TestResult] = []
        start_time = time.time()

        async with httpx.AsyncClient() as client:
            # Test preflight request
            test_start = time.time()
            try:
                response = await client.options(
                    f"{self.base_url}/api/health",
                    headers={
                        "Origin": "http://localhost:3000",
                        "Access-Control-Request-Method": "GET",
                    },
                )
                cors_header = response.headers.get("Access-Control-Allow-Origin", "")

                tests.append(
                    TestResult(
                        name="CORS Preflight",
                        description="OPTIONS Anfrage fuer CORS",
                        expected="Access-Control-Allow-Origin Header",
                        actual=cors_header or "(nicht gesetzt)",
                        status="passed" if cors_header else "skipped",
                        duration_ms=(time.time() - test_start) * 1000,
                    )
                )
            except Exception as e:
                tests.append(
                    TestResult(
                        name="CORS Preflight",
                        description="OPTIONS Anfrage fuer CORS",
                        expected="Access-Control-Allow-Origin Header",
                        actual="Fehler",
                        status="failed",
                        duration_ms=(time.time() - test_start) * 1000,
                        error_message=str(e),
                    )
                )

        passed = sum(1 for t in tests if t.status == "passed")
        return TestCategoryResult(
            category="cors",
            display_name=EDUCATION_CONTENT["cors"]["display_name"],
            description=EDUCATION_CONTENT["cors"]["description"],
            why_important=EDUCATION_CONTENT["cors"]["why_important"],
            tests=tests,
            passed=passed,
            failed=len(tests) - passed,
            total=len(tests),
            duration_ms=(time.time() - start_time) * 1000,
        )

    async def run_all(self) -> FullTestResults:
        """Run all middleware tests."""
        start_time = time.time()

        # Run all test categories
        categories = await asyncio.gather(
            self.test_request_id(),
            self.test_security_headers(),
            self.test_rate_limiter(),
            self.test_pii_redactor(),
            self.test_input_gate(),
            self.test_cors(),
        )

        total_passed = sum(c.passed for c in categories)
        total_failed = sum(c.failed for c in categories)
        total_tests = sum(c.total for c in categories)

        return FullTestResults(
            timestamp=datetime.now().isoformat(),
            categories=list(categories),
            total_passed=total_passed,
            total_failed=total_failed,
            total_tests=total_tests,
            duration_ms=(time.time() - start_time) * 1000,
        )


# ==============================================
# API Router
# ==============================================

router = APIRouter(prefix="/api/admin/ui-tests", tags=["ui-tests"])

# Global test runner instance
test_runner = MiddlewareTestRunner()


@router.post("/request-id", response_model=TestCategoryResult)
async def test_request_id():
    """Run Request-ID middleware tests."""
    return await test_runner.test_request_id()


@router.post("/security-headers", response_model=TestCategoryResult)
async def test_security_headers():
    """Run Security Headers middleware tests."""
    return await test_runner.test_security_headers()


@router.post("/rate-limiter", response_model=TestCategoryResult)
async def test_rate_limiter():
    """Run Rate Limiter middleware tests."""
    return await test_runner.test_rate_limiter()


@router.post("/pii-redactor", response_model=TestCategoryResult)
async def test_pii_redactor():
    """Run PII Redactor tests."""
    return await test_runner.test_pii_redactor()


@router.post("/input-gate", response_model=TestCategoryResult)
async def test_input_gate():
    """Run Input Gate middleware tests."""
    return await test_runner.test_input_gate()


@router.post("/cors", response_model=TestCategoryResult)
async def test_cors():
    """Run CORS middleware tests."""
    return await test_runner.test_cors()


@router.post("/run-all", response_model=FullTestResults)
async def run_all_tests():
    """Run all middleware tests."""
    return await test_runner.run_all()


@router.get("/education/{category}")
async def get_education_content(category: str):
    """Get educational content for a test category."""
    if category not in EDUCATION_CONTENT:
        raise HTTPException(status_code=404, detail=f"Category '{category}' not found")

    return EDUCATION_CONTENT[category]


@router.get("/categories")
async def list_categories():
    """List all available test categories."""
    return [
        {
            "id": key,
            "display_name": value["display_name"],
            "description": value["description"],
        }
        for key, value in EDUCATION_CONTENT.items()
    ]
