import json

from sentra.graph import build_evidence_graph
from sentra.models import CertificateFinding, ConfigHint, Device, Finding, FirmwareArtifact, NetworkScanArtifact, Service, ServiceHint, VulnerabilityMatch
from sentra.reporting.diff import build_scan_diff
from sentra.reporting.renderer import build_report


def test_build_report_markdown_includes_devices_risks_and_next_checks() -> None:
    network_scan = NetworkScanArtifact(
        target="192.168.1.0/24",
        devices=[
            Device(
                host="cam-01",
                ip_address="192.168.1.10",
                protocols=["web"],
                services=[
                    Service(port=80, protocol="tcp", service="http"),
                    Service(port=23, protocol="tcp", service="telnet"),
                ],
                findings=[
                    Finding(
                        title="Exposed admin panel",
                        severity="high",
                        confidence=0.9,
                        description="HTTP admin interface is reachable.",
                        tags=["web"],
                    ),
                    Finding(
                        title="Legacy telnet service exposed",
                        severity="critical",
                        confidence=0.92,
                        description="Telnet management is reachable without segmentation.",
                        tags=["telnet"],
                    )
                ],
                fingerprint_confidence=0.88,
                fingerprint_matches=["Acme Vision Camera"],
                vulnerability_matches=[
                    VulnerabilityMatch(
                        cve_id="CVE-2026-0101",
                        product="goahead",
                        normalized_product="goahead",
                        category="confirmed-match",
                        severity="high",
                        summary="Test correlation",
                        confidence=0.9,
                        matched_on=["product:goahead", "version:5.2"],
                        rationale="goahead matched as confirmed-match based on product and version evidence.",
                    )
                ],
            )
        ],
    )
    firmware_scan = FirmwareArtifact(
        path="firmware.bin",
        sha256="a" * 64,
        size_bytes=128,
        config_hints=[
            ConfigHint(path="etc/device.conf", key="password", value_preview="admin", category="credentials", confidence=0.9)
        ],
        service_hints=[
            ServiceHint(service="http", source_path="www/index.html", rationale="Found web root", confidence=0.8)
        ],
        certificate_findings=[
            CertificateFinding(path="certs/device.pem", common_name="device.local", confidence=0.82, rationale="embedded")
        ],
        findings=[
            Finding(
                title="Embedded credential reference",
                severity="high",
                confidence=0.8,
                description="Firmware contains credential-related strings.",
                tags=["credentials"],
            )
        ],
    )
    correlation = build_evidence_graph(network_scan, firmware_scan)

    artifact, content = build_report(network_scan, firmware_scan, correlation, "markdown")

    assert artifact.devices[0]["host"] == "cam-01"
    assert artifact.top_risky_devices[0]["host"] == "cam-01"
    assert artifact.grouped_findings[0]["services"][0]["findings"]
    assert artifact.weak_paths[0]["rank"] == 1
    assert artifact.weak_paths[0]["explanation"]
    assert "score" in artifact.weak_paths[0]
    assert artifact.recommended_checks
    assert len(artifact.risks) == 7
    assert artifact.intelligence_summary["highest_confidence_fingerprints"][0]["match"] == "Acme Vision Camera"
    assert artifact.intelligence_summary["strongest_vulnerability_correlations"][0]["cve_id"] == "CVE-2026-0101"
    assert "## Graph Overview" in content
    assert "## Executive Summary" in content
    assert "## Discovered Assets" in content
    assert "## Top Risky Devices" in content
    assert "## Findings by Host and Service" in content
    assert "## Weak Paths" in content
    assert "## Top Risky Paths" in content
    assert "## Firmware Analysis" in content
    assert "## Firmware Evidence" in content
    assert "## Recommended Next Checks" in content
    assert "## Intelligence Summary" in content
    assert "## Vulnerability Intelligence" in content
    assert "## Risks" in content
    assert "Exposed admin panel" in content
    assert "Embedded credential reference" in content
    assert "Legacy telnet service exposed" in content


def test_build_report_json_includes_structured_summaries() -> None:
    network_scan = NetworkScanArtifact(
        target="127.0.0.1/32",
        devices=[
            Device(
                host="127.0.0.1",
                ip_address="127.0.0.1",
                services=[Service(port=22, protocol="tcp", service="ssh")],
            )
        ],
    )
    correlation = build_evidence_graph(network_scan, None)

    _, content = build_report(network_scan, None, correlation, "json")
    payload = json.loads(content)

    assert payload["devices"][0]["ip_address"] == "127.0.0.1"
    assert "graph_overview" in payload
    assert payload["devices"][0]["services"] == ["ssh:22/tcp"]
    assert payload["risks"] == []
    assert payload["top_risky_devices"][0]["host"] == "127.0.0.1"
    assert payload["weak_paths"] == []
    assert payload["recommended_checks"]
    assert "graph_insights" in payload
    assert "intelligence_summary" in payload


def test_build_report_html_contains_tables_and_graph_data() -> None:
    network_scan = NetworkScanArtifact(
        target="127.0.0.1/32",
        devices=[
            Device(
                host="cam-01",
                ip_address="127.0.0.1",
                services=[Service(port=80, protocol="tcp", service="http", product="Boa")],
                findings=[
                    Finding(
                        title="Exposed admin panel",
                        severity="high",
                        confidence=0.9,
                        description="HTTP admin interface is reachable.",
                    )
                ],
            )
        ],
    )
    correlation = build_evidence_graph(network_scan, None)

    _, content = build_report(network_scan, None, correlation, "html")

    assert "<html" in content.lower()
    assert "Executive Summary" in content
    assert "Discovered Assets" in content
    assert "Grouped Findings" in content
    assert "Top Risky Devices" in content
    assert "graph-data" in content
    assert "Evidence graph overview" in content
    assert "<svg" in content
    assert "graph-visualization-data" in content
    assert "Top Risky Paths" in content
    assert "Firmware Evidence" in content
    assert "Vulnerability Intelligence" in content


def test_build_scan_diff_reports_added_removed_and_changed_hosts() -> None:
    before = NetworkScanArtifact(
        target="192.168.1.0/24",
        devices=[Device(host="cam-01", ip_address="192.168.1.10", services=[Service(port=80, protocol="tcp", service="http")])],
    )
    after = NetworkScanArtifact(
        target="192.168.1.0/24",
        devices=[
            Device(host="cam-01", ip_address="192.168.1.10", services=[Service(port=443, protocol="tcp", service="https")]),
            Device(host="cam-02", ip_address="192.168.1.11", services=[Service(port=22, protocol="tcp", service="ssh")]),
        ],
    )

    content = build_scan_diff(before, after, "json")
    payload = json.loads(content)

    assert payload["added_hosts"] == ["192.168.1.11"]
    assert payload["removed_hosts"] == []
    assert payload["changed_hosts"] == ["192.168.1.10"]
    assert payload["changed_details"][0]["host"] == "192.168.1.10"


def test_build_scan_diff_includes_firmware_and_correlation_changes() -> None:
    before_network = NetworkScanArtifact(target="t", devices=[])
    after_network = NetworkScanArtifact(target="t", devices=[])
    before_firmware = FirmwareArtifact(
        path="before.bin",
        sha256="1" * 64,
        size_bytes=10,
        findings=[Finding(title="Old signal", severity="low", confidence=0.5, description="old")],
    )
    after_firmware = FirmwareArtifact(
        path="after.bin",
        sha256="2" * 64,
        size_bytes=10,
        config_hints=[ConfigHint(path="etc/device.conf", key="password", value_preview="admin", category="credentials", confidence=0.9)],
        service_hints=[ServiceHint(service="http", source_path="www/index.html", rationale="web", confidence=0.8)],
        findings=[Finding(title="New signal", severity="high", confidence=0.8, description="new")],
    )
    before_correlation = build_evidence_graph(None, before_firmware)
    after_correlation = build_evidence_graph(None, after_firmware)

    payload = json.loads(
        build_scan_diff(
            before_network,
            after_network,
            "json",
            previous_firmware=before_firmware,
            current_firmware=after_firmware,
            previous_correlation=before_correlation,
            current_correlation=after_correlation,
        )
    )

    assert payload["firmware_changes"]["new_config_hints"] == ["password:etc/device.conf"]
    assert payload["firmware_changes"]["new_service_hints"] == ["http"]
    assert payload["firmware_changes"]["new_service_hint_signals"] == ["http:www/index.html"]
    assert payload["correlation_changes"]["changed_path_rankings"] == 1


def test_build_scan_diff_markdown_is_readable() -> None:
    before = NetworkScanArtifact(
        target="192.168.1.0/24",
        devices=[Device(host="cam-01", ip_address="192.168.1.10", services=[Service(port=80, protocol="tcp", service="http")])],
    )
    after = NetworkScanArtifact(
        target="192.168.1.0/24",
        devices=[Device(host="cam-01", ip_address="192.168.1.10", services=[Service(port=443, protocol="tcp", service="https")])],
    )

    content = build_scan_diff(before, after, "markdown")

    assert "## Summary" in content
    assert "## Changed Host Details" in content
    assert "## Firmware Analysis Changes" in content
    assert "## Correlated Evidence Changes" in content
    assert "Before services: http:80/tcp" in content
    assert "After services: https:443/tcp" in content


def test_build_scan_diff_html_includes_visual_sections() -> None:
    before = NetworkScanArtifact(
        target="192.168.1.0/24",
        devices=[Device(host="cam-01", ip_address="192.168.1.10", services=[Service(port=80, protocol="tcp", service="http")])],
    )
    after = NetworkScanArtifact(
        target="192.168.1.0/24",
        devices=[Device(host="cam-01", ip_address="192.168.1.10", services=[Service(port=443, protocol="tcp", service="https")])],
    )

    content = build_scan_diff(before, after, "html")

    assert "<html" in content.lower()
    assert "Sentra Scan Diff" in content
    assert "Changed Host Details" in content
    assert "Firmware Analysis Changes" in content
    assert "Correlated Evidence Changes" in content

