"""Test file-level Pro rules."""

import tempfile
from pathlib import Path

from apiposture.core.models.endpoint import Endpoint
from apiposture.core.models.enums import EndpointType, Framework, HttpMethod

from apiposture_pro.rules.file_level.ap016_insecure_dependencies import (
    AP016InsecureDependencies,
    _parse_version,
)


def test_ap016_detects_vulnerable_django() -> None:
    """Test AP016 detects vulnerable Django version."""
    rule = AP016InsecureDependencies()

    # Create a temp directory with requirements.txt
    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        req_file = tmppath / "requirements.txt"
        req_file.write_text("django==2.2.0\nrequests==2.28.0\n")

        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint = Endpoint(
            route="/api/test",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.DJANGO_DRF,
            endpoint_type=EndpointType.FUNCTION,
            function_name="test_view",
        )

        findings = list(rule.evaluate(endpoint))
        assert len(findings) > 0
        assert any("django" in f.message.lower() for f in findings)

        # Reset for next test
        rule._scanned_projects.clear()


def test_ap016_detects_vulnerable_requests() -> None:
    """Test AP016 detects vulnerable requests library."""
    rule = AP016InsecureDependencies()

    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        req_file = tmppath / "requirements.txt"
        req_file.write_text("requests==2.20.0\n")

        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint = Endpoint(
            route="/api/test",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.FLASK,
            endpoint_type=EndpointType.FUNCTION,
            function_name="test_route",
        )

        findings = list(rule.evaluate(endpoint))
        assert len(findings) > 0
        assert any("requests" in f.message.lower() for f in findings)

        rule._scanned_projects.clear()


def test_ap016_scans_once_per_project() -> None:
    """Test AP016 only scans each project once."""
    rule = AP016InsecureDependencies()

    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        req_file = tmppath / "requirements.txt"
        req_file.write_text("django==2.2.0\n")

        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint1 = Endpoint(
            route="/api/test1",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.DJANGO_DRF,
            endpoint_type=EndpointType.FUNCTION,
            function_name="test_view1",
        )

        endpoint2 = Endpoint(
            route="/api/test2",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=5,
            framework=Framework.DJANGO_DRF,
            endpoint_type=EndpointType.FUNCTION,
            function_name="test_view2",
        )

        # First scan should find issues
        findings1 = list(rule.evaluate(endpoint1))
        assert len(findings1) > 0

        # Second scan should be skipped (same project)
        findings2 = list(rule.evaluate(endpoint2))
        assert len(findings2) == 0

        rule._scanned_projects.clear()


def test_ap016_no_findings_for_missing_requirements() -> None:
    """Test AP016 handles missing requirements file gracefully."""
    rule = AP016InsecureDependencies()

    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint = Endpoint(
            route="/api/test",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.FASTAPI,
            endpoint_type=EndpointType.ROUTER,
            function_name="test_endpoint",
        )

        # Should not crash, just return no findings
        findings = list(rule.evaluate(endpoint))
        assert isinstance(findings, list)

        rule._scanned_projects.clear()


# --- Version comparison tests ---


def test_parse_version_basic() -> None:
    """Test _parse_version with basic version strings."""
    assert _parse_version("1.0.0") == (1, 0, 0)
    assert _parse_version("2.31.0") == (2, 31, 0)
    assert _parse_version("10.0.1") == (10, 0, 1)


def test_parse_version_comparison_2_3_vs_2_31() -> None:
    """Test version comparison: 2.3.0 < 2.31.0 (must be True)."""
    assert _parse_version("2.3.0") < _parse_version("2.31.0")


def test_parse_version_comparison_1_9_vs_1_10() -> None:
    """Test version comparison: 1.9.0 < 1.10.0 (must be True)."""
    assert _parse_version("1.9.0") < _parse_version("1.10.0")


def test_parse_version_comparison_equal() -> None:
    """Test version comparison: equal versions."""
    assert _parse_version("2.2.0") == _parse_version("2.2.0")
    assert not (_parse_version("2.2.0") < _parse_version("2.2.0"))


def test_parse_version_comparison_greater() -> None:
    """Test version comparison: greater version is not less."""
    assert not (_parse_version("3.0.0") < _parse_version("2.31.0"))


def test_parse_version_two_segment() -> None:
    """Test version comparison with two-segment versions."""
    assert _parse_version("6.0") < _parse_version("6.1")
    assert _parse_version("5.9") < _parse_version("6.0")


def test_ap016_version_comparison_in_requirements() -> None:
    """Test AP016 correctly compares version 2.20.0 < 2.31.0."""
    rule = AP016InsecureDependencies()

    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        req_file = tmppath / "requirements.txt"
        # requests 2.20.0 should be flagged as vulnerable (<2.31.0)
        req_file.write_text("requests==2.20.0\n")

        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint = Endpoint(
            route="/api/test",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.FASTAPI,
            endpoint_type=EndpointType.ROUTER,
            function_name="test_endpoint",
        )

        findings = list(rule.evaluate(endpoint))
        assert any("requests" in f.message.lower() for f in findings)

        rule._scanned_projects.clear()


def test_ap016_version_comparison_edge_case() -> None:
    """Test AP016 version comparison edge case: 1.9.0 vs 1.26.18 for urllib3."""
    rule = AP016InsecureDependencies()

    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        req_file = tmppath / "requirements.txt"
        # urllib3 1.9.0 should be flagged as vulnerable (<1.26.18)
        req_file.write_text("urllib3==1.9.0\n")

        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint = Endpoint(
            route="/api/test",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.FASTAPI,
            endpoint_type=EndpointType.ROUTER,
            function_name="test_endpoint",
        )

        findings = list(rule.evaluate(endpoint))
        assert any("urllib3" in f.message.lower() for f in findings)

        rule._scanned_projects.clear()


# --- Operator support tests ---


def test_ap016_detects_gte_operator() -> None:
    """Test AP016 detects vulnerable packages with >= operator."""
    rule = AP016InsecureDependencies()

    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        req_file = tmppath / "requirements.txt"
        # requests>=2.20.0 should be flagged (2.20.0 < 2.31.0)
        req_file.write_text("requests>=2.20.0\n")

        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint = Endpoint(
            route="/api/test",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.FASTAPI,
            endpoint_type=EndpointType.ROUTER,
            function_name="test_endpoint",
        )

        findings = list(rule.evaluate(endpoint))
        assert any("requests" in f.message.lower() for f in findings)

        rule._scanned_projects.clear()


def test_ap016_detects_compat_operator() -> None:
    """Test AP016 detects vulnerable packages with ~= operator."""
    rule = AP016InsecureDependencies()

    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        req_file = tmppath / "requirements.txt"
        # django~=2.2.0 should be flagged (2.2.0 < 2.2.28)
        req_file.write_text("django~=2.2.0\n")

        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint = Endpoint(
            route="/api/test",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.DJANGO_DRF,
            endpoint_type=EndpointType.FUNCTION,
            function_name="test_view",
        )

        findings = list(rule.evaluate(endpoint))
        assert any("django" in f.message.lower() for f in findings)

        rule._scanned_projects.clear()


# --- New vulnerable packages tests ---


def test_ap016_detects_vulnerable_werkzeug() -> None:
    """Test AP016 detects vulnerable werkzeug."""
    rule = AP016InsecureDependencies()

    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        req_file = tmppath / "requirements.txt"
        req_file.write_text("werkzeug==2.0.0\n")

        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint = Endpoint(
            route="/api/test",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.FLASK,
            endpoint_type=EndpointType.FUNCTION,
            function_name="test_route",
        )

        findings = list(rule.evaluate(endpoint))
        assert any("werkzeug" in f.message.lower() for f in findings)

        rule._scanned_projects.clear()


def test_ap016_detects_vulnerable_starlette() -> None:
    """Test AP016 detects vulnerable starlette."""
    rule = AP016InsecureDependencies()

    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        req_file = tmppath / "requirements.txt"
        req_file.write_text("starlette==0.20.0\n")

        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint = Endpoint(
            route="/api/test",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.FASTAPI,
            endpoint_type=EndpointType.ROUTER,
            function_name="test_endpoint",
        )

        findings = list(rule.evaluate(endpoint))
        assert any("starlette" in f.message.lower() for f in findings)

        rule._scanned_projects.clear()


# --- pyproject.toml PEP 621 tests ---


def test_ap016_scans_pyproject_pep621() -> None:
    """Test AP016 scans [project.dependencies] section (PEP 621)."""
    rule = AP016InsecureDependencies()

    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        pyproject = tmppath / "pyproject.toml"
        pyproject.write_text(
            '[project]\nname = "myproject"\n\n'
            "[project.dependencies]\n"
            '"requests>=2.20.0",\n'
            '"flask>=2.0.0",\n'
        )

        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint = Endpoint(
            route="/api/test",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.FASTAPI,
            endpoint_type=EndpointType.ROUTER,
            function_name="test_endpoint",
        )

        findings = list(rule.evaluate(endpoint))
        assert any("requests" in f.message.lower() or "flask" in f.message.lower() for f in findings)

        rule._scanned_projects.clear()


def test_ap016_instance_state_not_shared() -> None:
    """Test AP016 uses instance state, not class-level state."""
    rule1 = AP016InsecureDependencies()
    rule2 = AP016InsecureDependencies()

    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        req_file = tmppath / "requirements.txt"
        req_file.write_text("django==2.2.0\n")

        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint = Endpoint(
            route="/api/test",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.DJANGO_DRF,
            endpoint_type=EndpointType.FUNCTION,
            function_name="test_view",
        )

        # Scan with rule1
        findings1 = list(rule1.evaluate(endpoint))
        assert len(findings1) > 0

        # rule2 should also detect (separate instance, separate _scanned_projects)
        findings2 = list(rule2.evaluate(endpoint))
        assert len(findings2) > 0


# ======================================================================
# New tests for false positive fixes discovered via real-world scanning
# ======================================================================


def test_ap016_pyproject_no_version_not_flagged() -> None:
    """Test AP016 does NOT flag pyproject.toml deps without a parseable version."""
    rule = AP016InsecureDependencies()

    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        pyproject = tmppath / "pyproject.toml"
        # Poetry-style: jinja2 = "*" or just listed without version
        pyproject.write_text(
            '[tool.poetry.dependencies]\n'
            'python = "^3.10"\n'
            'jinja2 = "*"\n'
            'werkzeug = {extras = ["watchdog"]}\n'
        )

        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint = Endpoint(
            route="/api/test",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.FLASK,
            endpoint_type=EndpointType.FUNCTION,
            function_name="index",
        )

        findings = list(rule.evaluate(endpoint))
        # Should NOT flag jinja2 or werkzeug without parseable version
        assert not any("jinja2" in f.message.lower() for f in findings)
        assert not any("werkzeug" in f.message.lower() for f in findings)

        rule._scanned_projects.clear()


def test_ap016_pyproject_with_vulnerable_version_flagged() -> None:
    """Test AP016 DOES flag pyproject.toml deps with a specific vulnerable version."""
    rule = AP016InsecureDependencies()

    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        pyproject = tmppath / "pyproject.toml"
        pyproject.write_text(
            '[tool.poetry.dependencies]\n'
            'python = "^3.10"\n'
            'flask = "^2.0.0"\n'  # 2.0.0 < 2.3.0 — should be flagged
        )

        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint = Endpoint(
            route="/api/test",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.FLASK,
            endpoint_type=EndpointType.FUNCTION,
            function_name="index",
        )

        findings = list(rule.evaluate(endpoint))
        assert any("flask" in f.message.lower() for f in findings)

        rule._scanned_projects.clear()


def test_ap016_pep621_no_version_not_flagged() -> None:
    """Test AP016 does NOT flag PEP 621 deps without version specifier."""
    rule = AP016InsecureDependencies()

    with tempfile.TemporaryDirectory() as tmpdir:
        tmppath = Path(tmpdir)
        pyproject = tmppath / "pyproject.toml"
        pyproject.write_text(
            '[project]\nname = "myproject"\n\n'
            "dependencies = [\n"
            '  "jinja2",\n'
            '  "werkzeug",\n'
            "]\n"
        )

        api_file = tmppath / "api.py"
        api_file.write_text("# API code here")

        endpoint = Endpoint(
            route="/api/test",
            methods=[HttpMethod.GET],
            file_path=api_file,
            line_number=1,
            framework=Framework.FASTAPI,
            endpoint_type=EndpointType.ROUTER,
            function_name="index",
        )

        findings = list(rule.evaluate(endpoint))
        assert len(findings) == 0

        rule._scanned_projects.clear()
