import json
from pathlib import Path

from sentra.fingerprints import (
    apply_device_fingerprint,
    load_fingerprint_entries,
    rank_fingerprint_candidates,
    validate_fingerprint_entries,
)
from sentra.models import Device, Endpoint, Service


def test_fingerprint_matching_uses_metadata_tokens_and_endpoints() -> None:
    device = Device(
        host="gw-01",
        ip_address="192.168.1.50",
        protocols=["mqtt", "modbus", "web"],
        services=[
            Service(
                port=502,
                protocol="tcp",
                service="modbus",
                banner="Atlas Gateway 2.1",
                product="gateway",
                metadata={"modbus_probe": {"device_id": "Atlas Gateway 2.1", "unit_id": 1}},
            ),
            Service(
                port=443,
                protocol="tcp",
                service="https",
                product="nginx",
                metadata={"http_probe": {"headers": {"server": "nginx"}, "location": "/api/devices"}},
            ),
        ],
        endpoints=[Endpoint(url="https://192.168.1.50/api/devices", confidence=0.9)],
    )

    matched = apply_device_fingerprint(device)

    assert matched.vendor == "Atlas"
    assert matched.model == "Gateway"
    assert "Atlas IoT Gateway" in matched.fingerprint_matches
    assert any(item.kind == "fingerprint-metadata-match" for item in matched.fingerprint_evidence)
    assert any(item.kind == "fingerprint-endpoint-match" for item in matched.fingerprint_evidence)
    assert any(item.kind == "fingerprint-protocol-match" for item in matched.fingerprint_evidence)


def test_workspace_override_takes_precedence_for_matching(tmp_path: Path) -> None:
    override_path = tmp_path / ".sentra" / "fingerprint_overrides.json"
    override_path.parent.mkdir(parents=True, exist_ok=True)
    override_path.write_text(
        json.dumps(
            [
                {
                    "name": "Atlas IoT Gateway",
                    "vendor": "Atlas Labs",
                    "model": "Gateway X",
                    "services": ["mqtt", "modbus", "https"],
                    "products": ["gateway", "mosquitto", "modbus"],
                    "metadata_tokens": ["telemetry", "gateway", "fieldbus", "modbus"],
                    "endpoint_patterns": ["/api/devices", "/metrics"],
                    "protocol_hints": ["mqtt", "modbus", "web"],
                    "confidence": 0.93,
                    "priority": 20
                }
            ]
        ),
        encoding="utf-8",
    )
    device = Device(
        host="gw-01",
        ip_address="192.168.1.50",
        protocols=["mqtt", "modbus", "web"],
        services=[
            Service(port=502, protocol="tcp", service="modbus", banner="Atlas Gateway 2.1", product="gateway"),
            Service(port=443, protocol="tcp", service="https", product="nginx"),
        ],
        endpoints=[Endpoint(url="https://192.168.1.50/api/devices", confidence=0.9)],
    )

    matched = apply_device_fingerprint(device, workspace=tmp_path)

    assert matched.vendor == "Atlas Labs"
    assert matched.model == "Gateway X"
    assert matched.fingerprint_confidence >= 0.9


def test_fingerprint_validator_reports_bad_entries(tmp_path: Path) -> None:
    override_path = tmp_path / "fingerprints.json"
    override_path.write_text(
        json.dumps(
            [
                {
                    "name": "Bad Entry",
                    "vendor": "Unknown",
                    "model": "Unknown",
                    "services": [],
                    "products": [],
                    "metadata_tokens": [],
                    "endpoint_patterns": ["Admin"],
                    "protocol_hints": [],
                    "confidence": 0.2
                }
            ]
        ),
        encoding="utf-8",
    )

    result = validate_fingerprint_entries(user_override_paths=[override_path])

    assert result.entries_checked >= 1
    assert result.issue_count >= 2
    assert any(issue.fingerprint == "Bad Entry" for issue in result.issues)


def test_builtin_fingerprint_catalog_is_split_and_expanded() -> None:
    entries = load_fingerprint_entries()
    names = {entry.name for entry in entries}
    categories = {entry.category for entry in entries}

    assert len(entries) >= 50
    assert {"cameras", "gateways", "routers", "printers", "nas", "industrial"}.issubset(categories)
    assert "Atlas IoT Gateway" in names
    assert "Meridian Branch Router" in names
    assert "Meridian Office Printer" in names
    assert "Meridian PTZ Camera" in names


def test_ranked_candidates_prefer_specific_router_over_generic_profile() -> None:
    device = Device(
        host="router-01",
        ip_address="192.168.1.1",
        protocols=["web", "ssh"],
        services=[
            Service(port=80, protocol="tcp", service="http", product="uhttpd", banner="OpenWrt LuCI"),
            Service(port=22, protocol="tcp", service="ssh", product="dropbear"),
            Service(port=53, protocol="udp", service="dns", product="dnsmasq"),
        ],
        endpoints=[Endpoint(url="http://192.168.1.1/cgi-bin/luci", confidence=0.9)],
    )

    ranked = rank_fingerprint_candidates(device)

    assert ranked[0].entry.name == "OpenWrt Router"
    assert "Generic Embedded Linux Device" in [item.entry.name for item in ranked]
    assert "more product markers" in ranked[0].explanation or "more specific family profile" in ranked[0].explanation


def test_conflict_indicators_push_camera_candidates_down() -> None:
    device = Device(
        host="printer-01",
        ip_address="192.168.1.20",
        protocols=["web", "snmp"],
        services=[
            Service(port=80, protocol="tcp", service="http", product="printer", banner="OfficeJet admin"),
            Service(port=161, protocol="udp", service="snmp", product="ipp", metadata={"sysdescr": "printer toner status"}),
        ],
        endpoints=[Endpoint(url="http://192.168.1.20/printer/status", confidence=0.92)],
    )

    ranked = rank_fingerprint_candidates(device)
    top_names = [item.entry.name for item in ranked]

    assert top_names[0] == "Meridian Office Printer"
    assert "Acme Vision Camera" not in top_names[:2]


def test_legacy_override_file_remains_compatible(tmp_path: Path) -> None:
    override_path = tmp_path / "legacy-fingerprints.json"
    override_path.write_text(
        json.dumps(
            [
                {
                    "name": "Legacy Router Override",
                    "vendor": "LegacyCo",
                    "model": "Legacy Router",
                    "services": ["http", "ssh"],
                    "products": ["router", "nginx"],
                    "metadata_tokens": ["legacy", "router"],
                    "endpoint_patterns": ["/admin"],
                    "protocol_hints": ["web", "ssh"],
                    "confidence": 0.91,
                }
            ]
        ),
        encoding="utf-8",
    )
    device = Device(
        host="legacy-router",
        ip_address="10.0.0.1",
        protocols=["web", "ssh"],
        services=[
            Service(port=80, protocol="tcp", service="http", product="router", banner="Legacy router"),
            Service(port=22, protocol="tcp", service="ssh", product="nginx"),
        ],
        endpoints=[Endpoint(url="http://10.0.0.1/admin", confidence=0.9)],
    )

    matched = apply_device_fingerprint(device, user_override_paths=[override_path])

    assert matched.vendor == "LegacyCo"
    assert matched.model == "Legacy Router"
    assert matched.fingerprint_matches[0] == "Legacy Router Override"


def test_category_loading_and_override_merge_behavior(tmp_path: Path) -> None:
    override_path = tmp_path / ".sentra" / "fingerprint_overrides.json"
    override_path.parent.mkdir(parents=True, exist_ok=True)
    override_path.write_text(
        json.dumps(
            [
                {
                    "name": "OpenWrt Router",
                    "vendor": "OpenWrt Labs",
                    "model": "Router Pro",
                    "category": "routers",
                    "aliases": ["openwrt"],
                    "services": ["ssh", "http", "dns"],
                    "products": ["uhttpd", "dropbear", "dnsmasq"],
                    "metadata_tokens": ["openwrt", "luci", "dnsmasq", "router"],
                    "endpoint_patterns": ["/cgi-bin/luci"],
                    "protocol_hints": ["web", "ssh"],
                    "confidence": 0.95,
                    "priority": 25
                }
            ]
        ),
        encoding="utf-8",
    )

    entries = load_fingerprint_entries(workspace=tmp_path)
    match = next(entry for entry in entries if entry.name == "OpenWrt Router")

    assert match.vendor == "OpenWrt Labs"
    assert match.model == "Router Pro"
    assert match.source == "workspace"


def test_explanation_includes_strengths_penalties_and_comparison() -> None:
    device = Device(
        host="gw-02",
        ip_address="192.168.1.60",
        protocols=["mqtt", "modbus", "web"],
        services=[
            Service(port=502, protocol="tcp", service="modbus", product="gateway", banner="Atlas Gateway 2.1"),
            Service(port=443, protocol="tcp", service="https", product="nginx", metadata={"http_probe": {"headers": {"server": "nginx"}}}),
            Service(port=1883, protocol="tcp", service="mqtt", product="mosquitto"),
        ],
        endpoints=[Endpoint(url="https://192.168.1.60/api/devices", confidence=0.9)],
    )

    ranked = rank_fingerprint_candidates(device)
    explanation = ranked[0].explanation

    assert "matched strongest on" in explanation
    assert "penalties:" in explanation
    assert "confidence is" in explanation
    assert "outranked nearby candidate" in explanation

