import pytest
import yaml
from unittest.mock import AsyncMock, MagicMock, patch
from fastapi.testclient import TestClient

from aegis_ai.features.component.data_models import ComponentIntelligenceModel
from aegis_ai.features.cve.data_models import SuggestAffectedComponentsModel
from aegis_ai_web.src.main import app

# Create a TestClient instance based on your FastAPI app
client = TestClient(app)


def test_read_root():
    """
    Test the root endpoint to ensure it returns a 200 OK status.
    """
    response = client.get("/")
    assert response.status_code == 200


def test_yaml_openapi():
    """
    Test the root endpoint to ensure it returns a 200 OK status.
    """
    response = client.get("/openapi.yml")
    assert response.status_code == 200
    assert "application/vnd.oai.openapi" in response.headers["content-type"]
    try:
        openapi_spec = yaml.safe_load(response.text)
    except yaml.YAMLError:
        assert False, "Response is not valid YAML"

    assert isinstance(openapi_spec, dict)
    assert "openapi" in openapi_spec
    assert "info" in openapi_spec
    assert "paths" in openapi_spec

    assert openapi_spec["info"]["title"] == "Aegis REST-API"


@pytest.mark.parametrize(
    "invalid_json_content,description",
    [
        ("invalid json content", "completely invalid JSON"),
        ('{"cve_id": "CVE-2025-12345"', "malformed JSON (missing closing brace)"),
        ("", "empty body"),
    ],
)
def test_invalid_json_request_body(invalid_json_content, description):
    """
    Test that invalid JSON in request body returns 400 with proper error message
    instead of leaking a full traceback.
    """
    response = client.post(
        "/api/v1/analysis/cve/suggest-impact",
        content=invalid_json_content,
        headers={"Content-Type": "application/json"},
    )
    assert response.status_code == 400
    assert "detail" in response.json()
    assert "Invalid JSON" in response.json()["detail"]
    # Verify traceback is not present in response
    assert "Traceback" not in response.text
    assert "stack" not in response.text


def test_missing_cve_id_field():
    """
    Test that missing 'cve_id' field in request body returns 400 with proper error message
    instead of leaking a full traceback.
    """
    response = client.post(
        "/api/v1/analysis/cve/suggest-impact",
        json={},  # Empty JSON body - missing 'cve_id' field
        headers={"Content-Type": "application/json"},
    )
    assert response.status_code == 400
    assert "detail" in response.json()
    assert "Missing required field: 'cve_id'" in response.json()["detail"]
    # Verify traceback is not present in response
    assert "Traceback" not in response.text
    assert "stack" not in response.text


def test_invalid_utf8_encoding():
    """
    Test that invalid UTF-8 encoding in request body returns 400 with proper error message
    instead of leaking a full traceback.

    Note: This test sends raw bytes with invalid UTF-8. The UnicodeDecodeError may occur
    during body reading or JSON parsing, but should be caught by the global handler.
    """
    # Create invalid UTF-8 bytes (invalid continuation byte \x80)
    # This simulates the curl example: curl -d $'{\x80'
    invalid_utf8 = b"{\x80"
    response = client.post(
        "/api/v1/analysis/cve/suggest-impact",
        content=invalid_utf8,
        headers={"Content-Type": "application/json"},
    )
    assert response.status_code == 400
    assert "detail" in response.json()
    # The error might be caught as JSONDecodeError or UnicodeDecodeError
    # depending on when the encoding error occurs
    detail = response.json()["detail"]
    assert "Invalid" in detail and (
        "JSON" in detail or "UTF-8" in detail or "encoding" in detail
    )
    # Verify traceback is not present in response
    assert "Traceback" not in response.text
    assert "stack" not in response.text


def test_component_intelligence_endpoint_uses_component_name_context():
    """
    Test that component-intelligence endpoint succeeds when context has component_name
    only (no cve_id). Regression test for fix: cve_id not in context for
    component_intelligence.
    """
    mock_output = ComponentIntelligenceModel(
        component_name="curl",
        component_latest_version="8.10.0",
        component_purl="pkg:generic/curl@8.10.0",
        website_url="https://curl.se",
        repo_url="https://github.com/curl/curl",
        popularity_score=1,
        stability_score=1,
        recent_news="Recent release.",
        active_contributors="Core team.",
        security_information="No critical issues.",
        further_learning="See docs.",
        explanation="Test mock.",
        confidence=0.95,
        tools_used=[],
        disclaimer="This response was generated by Aegis AI (https://github.com/RedHatProductSecurity/aegis-ai) using generative AI for informational purposes. All findings should be validated by a human expert.",
    )
    mock_result = MagicMock()
    mock_result.output = mock_output
    mock_result._state = MagicMock()
    mock_result._state.usage = MagicMock()
    mock_result._state.usage.input_tokens = 0

    with patch(
        "aegis_ai.features.Feature._run",
        new_callable=AsyncMock,
        return_value=mock_result,
    ):
        response = client.get(
            "/api/v1/analysis/component",
            params={"feature": "component-intelligence", "component_name": "curl"},
        )
    assert response.status_code == 200
    data = response.json()
    assert data["component_name"] == "curl"
    assert "popularity_score" in data


def test_suggest_affected_components_endpoint():
    """Test that suggest-affected-components CVE endpoint exists and returns full response shape."""
    mock_output = SuggestAffectedComponentsModel(
        cve_id="CVE-2024-1234",
        components=["kernel", "linux-kernel"],
        explanation="Mock explanation.",
        confidence=0.95,
        tools_used=["osidb_tool"],
        disclaimer=(
            "This response was generated by Aegis AI "
            "(https://github.com/RedHatProductSecurity/aegis-ai) using generative AI "
            "for informational purposes. All findings should be validated by a human expert."
        ),
    )
    mock_result = MagicMock()
    mock_result.output = mock_output
    mock_result._state = MagicMock()
    mock_result._state.usage = MagicMock()
    mock_result._state.usage.input_tokens = 0

    with patch(
        "aegis_ai.features.Feature._run",
        new_callable=AsyncMock,
        return_value=mock_result,
    ):
        response = client.get(
            "/api/v1/analysis/cve",
            params={
                "feature": "suggest-affected-components",
                "cve_id": "CVE-2024-1234",
            },
        )
    assert response.status_code == 200
    data = response.json()

    # Core fields
    assert data["cve_id"] == mock_output.cve_id
    assert data["components"] == mock_output.components

    # Shape / type validation for additional fields
    assert "explanation" in data
    assert isinstance(data["explanation"], str)

    assert "confidence" in data
    assert isinstance(data["confidence"], (float, int))

    assert "tools_used" in data
    assert isinstance(data["tools_used"], list)
    assert "osidb_tool" in data["tools_used"]

    assert "disclaimer" in data
    assert isinstance(data["disclaimer"], str)
