"""Tests for HTML builder functions in html.py."""


from meshmon.html import (
    COMPANION_CHART_GROUPS,
    PERIOD_CONFIG,
    REPEATER_CHART_GROUPS,
    _build_traffic_table_rows,
    build_chart_groups,
    build_companion_metrics,
    build_node_details,
    build_radio_config,
    build_repeater_metrics,
    get_jinja_env,
)


class TestBuildTrafficTableRows:
    """Test _build_traffic_table_rows function."""

    def test_empty_list(self):
        """Empty list returns empty list."""
        result = _build_traffic_table_rows([])
        assert result == []

    def test_rx_tx_packets(self):
        """RX and TX become Packets row."""
        traffic = [
            {"label": "RX", "value": "1.2k", "raw_value": 1200, "unit": "packets"},
            {"label": "TX", "value": "800", "raw_value": 800, "unit": "packets"},
        ]
        result = _build_traffic_table_rows(traffic)

        assert len(result) == 1
        assert result[0]["label"] == "Packets"
        assert result[0]["rx"] == "1.2k"
        assert result[0]["rx_raw"] == 1200
        assert result[0]["tx"] == "800"
        assert result[0]["tx_raw"] == 800
        assert result[0]["unit"] == "packets"

    def test_flood_rx_tx(self):
        """Flood RX/TX become Flood row."""
        traffic = [
            {"label": "Flood RX", "value": "500", "raw_value": 500, "unit": "packets"},
            {"label": "Flood TX", "value": "300", "raw_value": 300, "unit": "packets"},
        ]
        result = _build_traffic_table_rows(traffic)

        assert len(result) == 1
        assert result[0]["label"] == "Flood"
        assert result[0]["rx"] == "500"
        assert result[0]["tx"] == "300"
        assert result[0]["rx_raw"] == 500
        assert result[0]["tx_raw"] == 300
        assert result[0]["unit"] == "packets"

    def test_direct_rx_tx(self):
        """Direct RX/TX become Direct row."""
        traffic = [
            {"label": "Direct RX", "value": "200", "raw_value": 200, "unit": "packets"},
            {"label": "Direct TX", "value": "100", "raw_value": 100, "unit": "packets"},
        ]
        result = _build_traffic_table_rows(traffic)

        assert len(result) == 1
        assert result[0]["label"] == "Direct"
        assert result[0]["rx"] == "200"
        assert result[0]["tx"] == "100"
        assert result[0]["rx_raw"] == 200
        assert result[0]["tx_raw"] == 100
        assert result[0]["unit"] == "packets"

    def test_airtime_rx_tx(self):
        """Airtime TX/RX become Airtime row."""
        traffic = [
            {"label": "Airtime TX", "value": "1h 30m", "raw_value": 5400, "unit": "seconds"},
            {"label": "Airtime RX", "value": "3h 0m", "raw_value": 10800, "unit": "seconds"},
        ]
        result = _build_traffic_table_rows(traffic)

        assert len(result) == 1
        assert result[0]["label"] == "Airtime"
        assert result[0]["tx"] == "1h 30m"
        assert result[0]["rx"] == "3h 0m"
        assert result[0]["rx_raw"] == 10800
        assert result[0]["tx_raw"] == 5400
        assert result[0]["unit"] == "seconds"

    def test_output_order(self):
        """Output follows order: Packets, Flood, Direct, Airtime."""
        traffic = [
            {"label": "Airtime TX", "value": "1h", "raw_value": 3600, "unit": "seconds"},
            {"label": "Direct RX", "value": "100", "raw_value": 100, "unit": "packets"},
            {"label": "Flood RX", "value": "200", "raw_value": 200, "unit": "packets"},
            {"label": "RX", "value": "500", "raw_value": 500, "unit": "packets"},
        ]
        result = _build_traffic_table_rows(traffic)

        labels = [r["label"] for r in result]
        assert labels == ["Packets", "Flood", "Direct", "Airtime"]

    def test_missing_pair(self):
        """Missing pair leaves None for that direction."""
        traffic = [
            {"label": "RX", "value": "500", "raw_value": 500, "unit": "packets"},
        ]
        result = _build_traffic_table_rows(traffic)

        assert result[0]["rx"] == "500"
        assert result[0]["tx"] is None
        assert result[0]["rx_raw"] == 500
        assert result[0]["tx_raw"] is None

    def test_unrecognized_label_skipped(self):
        """Unrecognized labels are skipped."""
        traffic = [
            {"label": "Unknown", "value": "100", "raw_value": 100, "unit": "packets"},
            {"label": "RX", "value": "500", "raw_value": 500, "unit": "packets"},
        ]
        result = _build_traffic_table_rows(traffic)

        assert len(result) == 1
        assert result[0]["label"] == "Packets"


class TestBuildNodeDetails:
    """Test build_node_details function."""

    def test_repeater_details(self, configured_env, monkeypatch):
        """Repeater node details include location info."""
        monkeypatch.setenv("REPORT_LOCATION_SHORT", "Test Location")
        monkeypatch.setenv("REPORT_LAT", "51.5074")
        monkeypatch.setenv("REPORT_LON", "-0.1278")
        monkeypatch.setenv("REPORT_ELEV", "11")
        monkeypatch.setenv("REPORT_ELEV_UNIT", "m")
        monkeypatch.setenv("REPEATER_HARDWARE", "RAK 4631")

        # Reset config to pick up new values
        import meshmon.env
        meshmon.env._config = None

        result = build_node_details("repeater")

        labels = [d["label"] for d in result]
        assert "Location" in labels
        assert "Coordinates" in labels
        assert "Elevation" in labels
        assert "Hardware" in labels

        # Check specific values
        location = next(d for d in result if d["label"] == "Location")
        assert location["value"] == "Test Location"
        coords = next(d for d in result if d["label"] == "Coordinates")
        assert coords["value"] == "51.5074°N, 0.1278°W"
        elevation = next(d for d in result if d["label"] == "Elevation")
        assert elevation["value"] == "11 m"

        hardware = next(d for d in result if d["label"] == "Hardware")
        assert hardware["value"] == "RAK 4631"

    def test_companion_details(self, configured_env, monkeypatch):
        """Companion node details are simpler."""
        monkeypatch.setenv("COMPANION_HARDWARE", "T-Beam Supreme")

        # Reset config
        import meshmon.env
        meshmon.env._config = None

        result = build_node_details("companion")

        labels = [d["label"] for d in result]
        assert "Hardware" in labels
        assert "Connection" in labels
        assert next(d for d in result if d["label"] == "Connection")["value"] == "USB Serial"
        assert next(d for d in result if d["label"] == "Hardware")["value"] == "T-Beam Supreme"

        # No location info for companion
        assert "Location" not in labels
        assert "Coordinates" not in labels

    def test_coordinate_directions(self, configured_env, monkeypatch):
        """Coordinate directions are correct for positive/negative."""
        monkeypatch.setenv("REPORT_LAT", "-33.8688")  # Sydney
        monkeypatch.setenv("REPORT_LON", "151.2093")

        import meshmon.env
        meshmon.env._config = None

        result = build_node_details("repeater")
        coords = next(d for d in result if d["label"] == "Coordinates")

        assert coords["value"] == "33.8688°S, 151.2093°E"


class TestBuildRadioConfig:
    """Test build_radio_config function."""

    def test_returns_radio_settings(self, configured_env, monkeypatch):
        """Returns radio configuration from env."""
        monkeypatch.setenv("RADIO_FREQUENCY", "915.0 MHz")
        monkeypatch.setenv("RADIO_BANDWIDTH", "125 kHz")
        monkeypatch.setenv("RADIO_SPREAD_FACTOR", "SF12")
        monkeypatch.setenv("RADIO_CODING_RATE", "CR5")

        import meshmon.env
        meshmon.env._config = None

        result = build_radio_config()

        labels = [d["label"] for d in result]
        assert "Frequency" in labels
        assert "Bandwidth" in labels
        assert "Spread Factor" in labels
        assert "Coding Rate" in labels

        freq = next(d for d in result if d["label"] == "Frequency")
        assert freq["value"] == "915.0 MHz"
        assert next(d for d in result if d["label"] == "Bandwidth")["value"] == "125 kHz"
        assert next(d for d in result if d["label"] == "Spread Factor")["value"] == "SF12"
        assert next(d for d in result if d["label"] == "Coding Rate")["value"] == "CR5"


class TestBuildRepeaterMetrics:
    """Test build_repeater_metrics function."""

    def test_none_row_returns_empty(self):
        """None row returns empty metric lists."""
        result = build_repeater_metrics(None)
        assert result["critical_metrics"] == []
        assert result["secondary_metrics"] == []
        assert result["traffic_metrics"] == []

    def test_empty_row_returns_empty(self):
        """Empty row returns empty metric lists."""
        result = build_repeater_metrics({})
        assert result["critical_metrics"] == []
        assert result["secondary_metrics"] == []
        assert result["traffic_metrics"] == []

    def test_full_row_extracts_metrics(self):
        """Full row extracts all metric categories."""
        row = {
            "bat": 3850.0,
            "bat_pct": 55.0,
            "last_rssi": -85.0,
            "last_snr": 7.5,
            "uptime": 86400,
            "noise_floor": -115.0,
            "tx_queue_len": 0,
            "nb_recv": 1234,
            "nb_sent": 567,
            "recv_flood": 500,
            "sent_flood": 200,
            "recv_direct": 100,
            "sent_direct": 50,
            "airtime": 3600,
            "rx_airtime": 7200,
        }
        result = build_repeater_metrics(row)

        # Critical metrics
        assert [m["label"] for m in result["critical_metrics"]] == [
            "Battery",
            "Charge",
            "RSSI",
            "SNR",
        ]
        battery = result["critical_metrics"][0]
        assert battery == {
            "value": "3.85",
            "unit": "V",
            "label": "Battery",
            "bar_pct": 55,
        }
        assert result["critical_metrics"][1] == {
            "value": "55",
            "unit": "%",
            "label": "Charge",
        }
        assert result["critical_metrics"][2] == {
            "value": "-85",
            "unit": "dBm",
            "label": "RSSI",
        }
        assert result["critical_metrics"][3] == {
            "value": "7.50",
            "unit": "dB",
            "label": "SNR",
        }

        # Secondary metrics
        assert result["secondary_metrics"] == [
            {"label": "Uptime", "value": "1d 0h"},
            {"label": "Noise Floor", "value": "-115 dBm"},
            {"label": "TX Queue", "value": "0"},
        ]

        # Traffic metrics
        assert [
            (metric["label"], metric["value"], metric["raw_value"], metric["unit"])
            for metric in result["traffic_metrics"]
        ] == [
            ("RX", "1,234", 1234, "packets"),
            ("TX", "567", 567, "packets"),
            ("Flood RX", "500", 500, "packets"),
            ("Flood TX", "200", 200, "packets"),
            ("Direct RX", "100", 100, "packets"),
            ("Direct TX", "50", 50, "packets"),
            ("Airtime TX", "1h 0m", 3600, "seconds"),
            ("Airtime RX", "2h 0m", 7200, "seconds"),
        ]

    def test_battery_converts_mv_to_v(self):
        """Battery value is converted from mV to V."""
        row = {"bat": 4200.0, "bat_pct": 100.0}
        result = build_repeater_metrics(row)

        battery = next(m for m in result["critical_metrics"] if m["label"] == "Battery")
        assert battery["value"] == "4.20"

    def test_bar_pct_for_battery(self):
        """Battery has bar_pct for progress display."""
        row = {"bat": 3850.0, "bat_pct": 55.0}
        result = build_repeater_metrics(row)

        battery = next(m for m in result["critical_metrics"] if m["label"] == "Battery")
        assert battery["bar_pct"] == 55


class TestBuildCompanionMetrics:
    """Test build_companion_metrics function."""

    def test_none_row_returns_empty(self):
        """None row returns empty metric lists."""
        result = build_companion_metrics(None)
        assert result["critical_metrics"] == []
        assert result["secondary_metrics"] == []
        assert result["traffic_metrics"] == []

    def test_full_row_extracts_metrics(self):
        """Full row extracts all metric categories."""
        row = {
            "battery_mv": 3850.0,
            "bat_pct": 55.0,
            "contacts": 5,
            "uptime_secs": 86400,
            "recv": 1234,
            "sent": 567,
        }
        result = build_companion_metrics(row)

        # Critical metrics
        assert result["critical_metrics"] == [
            {
                "value": "3.85",
                "unit": "V",
                "label": "Battery",
                "bar_pct": 55,
            },
            {"value": "55", "unit": "%", "label": "Charge"},
            {"value": "5", "unit": None, "label": "Contacts"},
            {"value": "1d 0h", "unit": None, "label": "Uptime"},
        ]

        # Secondary metrics (empty for companion)
        assert result["secondary_metrics"] == []

        # Traffic metrics
        assert result["traffic_metrics"] == [
            {"label": "RX", "value": "1,234", "raw_value": 1234, "unit": "packets"},
            {"label": "TX", "value": "567", "raw_value": 567, "unit": "packets"},
        ]

    def test_battery_converts_mv_to_v(self):
        """Battery value is converted from mV to V."""
        row = {"battery_mv": 4000.0, "bat_pct": 80.0}
        result = build_companion_metrics(row)

        battery = next(m for m in result["critical_metrics"] if m["label"] == "Battery")
        assert battery["value"] == "4.00"

    def test_contacts_displays_correctly(self):
        """Contacts are displayed as integer."""
        row = {"contacts": 7}
        result = build_companion_metrics(row)

        contacts = next(m for m in result["critical_metrics"] if m["label"] == "Contacts")
        assert contacts["value"] == "7"
        assert contacts["unit"] is None


class TestGetJinjaEnv:
    """Test get_jinja_env function."""

    def test_returns_environment(self):
        """Returns a Jinja2 Environment."""
        from jinja2 import Environment

        # Reset singleton for clean test
        import meshmon.html
        meshmon.html._jinja_env = None

        env = get_jinja_env()
        assert isinstance(env, Environment)

    def test_returns_singleton(self):
        """Returns same instance on subsequent calls."""
        import meshmon.html
        meshmon.html._jinja_env = None

        env1 = get_jinja_env()
        env2 = get_jinja_env()
        assert env1 is env2

    def test_registers_custom_filters(self):
        """Custom format filters are registered."""
        import meshmon.html
        meshmon.html._jinja_env = None

        env = get_jinja_env()

        assert "format_time" in env.filters
        assert "format_value" in env.filters
        assert "format_number" in env.filters
        assert "format_duration" in env.filters
        assert "format_uptime" in env.filters
        assert "format_compact_number" in env.filters
        assert "format_duration_compact" in env.filters


class TestChartGroupConstants:
    """Test chart group configuration constants."""

    def test_repeater_chart_groups_defined(self):
        """Repeater chart groups are defined."""
        assert len(REPEATER_CHART_GROUPS) > 0
        for group in REPEATER_CHART_GROUPS:
            assert "title" in group
            assert "metrics" in group
            assert len(group["metrics"]) > 0

    def test_companion_chart_groups_defined(self):
        """Companion chart groups are defined."""
        assert len(COMPANION_CHART_GROUPS) > 0
        for group in COMPANION_CHART_GROUPS:
            assert "title" in group
            assert "metrics" in group
            assert len(group["metrics"]) > 0

    def test_period_config_defined(self):
        """Period config has all expected periods."""
        assert "day" in PERIOD_CONFIG
        assert "week" in PERIOD_CONFIG
        assert "month" in PERIOD_CONFIG
        assert "year" in PERIOD_CONFIG

        for _period, (title, subtitle) in PERIOD_CONFIG.items():
            assert isinstance(title, str)
            assert isinstance(subtitle, str)


class TestBuildChartGroups:
    """Tests for build_chart_groups."""

    def test_png_paths_use_relative_prefix(self, configured_env):
        """PNG fallback paths respect provided asset prefix."""
        out_dir = configured_env["out_dir"]
        asset_dir = out_dir / "assets" / "repeater"
        asset_dir.mkdir(parents=True, exist_ok=True)
        (asset_dir / "bat_day_light.png").write_bytes(b"fake")

        groups = build_chart_groups(
            role="repeater",
            period="day",
            chart_stats={},
            asset_prefix="../",
        )

        chart = next(
            chart
            for group in groups
            for chart in group["charts"]
            if chart["metric"] == "bat"
        )

        assert chart["use_svg"] is False
        assert chart["src_light"] == "../assets/repeater/bat_day_light.png"
        assert chart["src_dark"] == "../assets/repeater/bat_day_dark.png"
