"""
Test Prometheus/VictoriaMetrics name sanitization.

This ensures that illegal characters in series names and tag names
are automatically replaced with underscores.
"""
from __future__ import annotations

from contextlib import aclosing

from asyncvictoria import DS, Entry, sanitize_entry, sanitize_label_name, sanitize_metric_name
from asyncvictoria.mock import VictoriaTester


def test_sanitize_metric_name():
    """Test metric name sanitization."""
    # Valid names should not change
    assert sanitize_metric_name("cpu_usage") == "cpu_usage"
    assert sanitize_metric_name("cpu:usage") == "cpu:usage"
    assert sanitize_metric_name("_cpu") == "_cpu"
    assert sanitize_metric_name(":cpu") == ":cpu"

    # Invalid characters should be replaced with underscores
    assert sanitize_metric_name("cpu-usage") == "cpu_usage"
    assert sanitize_metric_name("cpu.usage") == "cpu_usage"
    assert sanitize_metric_name("cpu usage") == "cpu_usage"
    assert sanitize_metric_name("cpu@usage") == "cpu_usage"
    assert sanitize_metric_name("cpu/usage") == "cpu_usage"

    # First character rules
    assert sanitize_metric_name("123cpu") == "_23cpu"
    assert sanitize_metric_name("-cpu") == "_cpu"
    assert sanitize_metric_name(".cpu") == "_cpu"

    # Edge cases
    assert sanitize_metric_name("") == "_"
    assert sanitize_metric_name("123") == "_23"
    assert sanitize_metric_name("cpu-usage-total") == "cpu_usage_total"


def test_sanitize_label_name():
    """Test label (tag) name sanitization."""
    # Valid names should not change
    assert sanitize_label_name("host") == "host"
    assert sanitize_label_name("_host") == "_host"
    assert sanitize_label_name("host123") == "host123"

    # Colons are NOT allowed in label names (only in metric names)
    assert sanitize_label_name("host:name") == "host_name"

    # Invalid characters should be replaced with underscores
    assert sanitize_label_name("host-name") == "host_name"
    assert sanitize_label_name("host.name") == "host_name"
    assert sanitize_label_name("host name") == "host_name"
    assert sanitize_label_name("host@name") == "host_name"

    # First character rules
    assert sanitize_label_name("123host") == "_23host"
    assert sanitize_label_name("-host") == "_host"
    assert sanitize_label_name(":host") == "_host"

    # Edge cases
    assert sanitize_label_name("") == "_"
    assert sanitize_label_name("123") == "_23"


def test_sanitize_entry():
    """Test Entry sanitization."""
    # Entry with illegal characters
    e = Entry(
        series="cpu-usage.total",
        time=1500000000,
        value=123,
        tags={"host-name": "server1", "data.center": "us-west", "env:type": "prod"},
        mode=DS.gauge,
    )

    sanitized = sanitize_entry(e)

    # Check metric name was sanitized
    assert sanitized.series == "cpu_usage_total"

    # Check tag names were sanitized
    assert "host_name" in sanitized.tags
    assert "data_center" in sanitized.tags
    assert "env_type" in sanitized.tags

    # Check tag values are unchanged
    assert sanitized.tags["host_name"] == "server1"
    assert sanitized.tags["data_center"] == "us-west"
    assert sanitized.tags["env_type"] == "prod"

    # Check other fields are preserved
    assert sanitized.value == 123
    assert sanitized.ns_time == e.ns_time
    assert sanitized.mode == DS.gauge


async def test_write_with_illegal_names(free_tcp_port_factory):
    """Test writing data with illegal metric and tag names."""
    async with VictoriaTester(free_tcp_port_factory(), free_tcp_port_factory()).run() as tc:
        # Write entry with illegal characters
        e = Entry(
            series="test-metric.name",
            time=1500000000,
            value=456,
            tags={"host-name": "server1", "data.center": "dc1"},
            mode=DS.gauge,
        )
        await tc.put(e)
        await tc.flush()
        await tc.force_flush()

        # Query using the sanitized names (library automatically sanitizes)
        n = 0
        gd = tc.get_data("test-metric.name", tags={"host-name": "server1"},
                        t_start=1499999000, t_end=1500001000)
        async with aclosing(gd):
            async for x in gd:
                n += 1
                assert x.value == 456
                # The series name should be sanitized in storage
                assert x.series == "test_metric_name"

        assert n == 1, f"Expected 1 data point, got {n}"


async def test_query_with_illegal_names(free_tcp_port_factory):
    """Test querying data using illegal names (should be auto-sanitized)."""
    async with VictoriaTester(free_tcp_port_factory(), free_tcp_port_factory()).run() as tc:
        # Write with clean names
        e = Entry(
            series="cpu_usage",
            time=1500000000,
            value=789,
            tags={"hostname": "server2"},
            mode=DS.gauge,
        )
        await tc.put(e)
        await tc.flush()
        await tc.force_flush()

        # Query using illegal characters (should be auto-sanitized by library)
        n = 0
        gd = tc.get_data("cpu-usage", tags={"host-name": "server2"},
                        t_start=1499999000, t_end=1500001000)
        async with aclosing(gd):
            async for x in gd:
                n += 1

        # Should not find anything because the sanitized query doesn't match
        # cpu-usage -> cpu_usage (matches)
        # but host-name -> host_name (doesn't match hostname)
        assert n == 0, "Should not find data with mismatched tag names"

        # Now query with matching sanitized name
        n = 0
        gd = tc.get_data("cpu-usage", tags={"hostname": "server2"},
                        t_start=1499999000, t_end=1500001000)
        async with aclosing(gd):
            async for x in gd:
                n += 1
                assert x.value == 789

        assert n == 1, f"Expected 1 data point, got {n}"


async def test_roundtrip_with_special_characters(free_tcp_port_factory):
    """Test that data written with special characters can be read back."""
    async with VictoriaTester(free_tcp_port_factory(), free_tcp_port_factory()).run() as tc:
        # Write multiple entries with various illegal characters
        test_cases = [
            ("metric-with-dashes", {"tag-with-dash": "value1"}, 100),
            ("metric.with.dots", {"tag.with.dot": "value2"}, 200),
            ("metric@with@ats", {"tag@with@at": "value3"}, 300),
            ("metric/with/slashes", {"tag/with/slash": "value4"}, 400),
        ]

        for series, tags, value in test_cases:
            e = Entry(
                series=series,
                time=1500000000,
                value=value,
                tags=tags,
                mode=DS.gauge,
            )
            await tc.put(e)

        await tc.flush()
        await tc.force_flush()

        # Read back each entry using the original (illegal) names
        # The library should sanitize them automatically
        for series, tags, expected_value in test_cases:
            n = 0
            gd = tc.get_data(series, tags=tags, t_start=1499999000, t_end=1500001000)
            async with aclosing(gd):
                async for x in gd:
                    n += 1
                    assert x.value == expected_value, f"Expected {expected_value}, got {x.value}"

            assert n == 1, f"Expected 1 data point for {series}, got {n}"


async def test_metric_name_first_character_rules(free_tcp_port_factory):
    """Test that metric names starting with invalid characters are handled."""
    async with VictoriaTester(free_tcp_port_factory(), free_tcp_port_factory()).run() as tc:
        # Metric names starting with numbers or special chars
        # Use different names so they don't collide after sanitization
        test_cases = [
            ("123metric_a", 100),  # starts with number -> _23metric_a
            ("-metric-b", 200),     # starts with dash -> _metric_b
            (".metric.c", 300),     # starts with dot -> _metric_c
        ]

        for series, value in test_cases:
            e = Entry(
                series=series,
                time=1500000000,
                value=value,
                tags={},
                mode=DS.gauge,
            )
            await tc.put(e)

        await tc.flush()
        await tc.force_flush()

        # Query back using original names (will be auto-sanitized)
        for series, expected_value in test_cases:
            n = 0
            gd = tc.get_data(series, tags={}, t_start=1499999000, t_end=1500001000)
            async with aclosing(gd):
                async for x in gd:
                    n += 1
                    assert x.value == expected_value, \
                        f"For series {series}, expected {expected_value}, got {x.value}"

            assert n == 1, f"Expected 1 data point for {series}, got {n}"
