from pathlib import Path
from shutil import copyfile
from tempfile import TemporaryDirectory

import toml

from arrakis_server.metadata import ChannelConfigBackend
from arrakis_server.partition import PartitionMetadata, partition_channels
from arrakis_server.tests.test_partition import create_channel


def setup_metadata(dest_dir: str | Path, metdata_filename: str) -> Path:
    dest_path = Path(dest_dir) / "metdata.toml"
    src_path = Path(__file__).parent / "data" / metdata_filename
    copyfile(src_path, dest_path)
    return dest_path


def test_channel_config_backend_load_metadata():
    with TemporaryDirectory() as cache_dir:
        cache_path = setup_metadata(cache_dir, "metadata_test_1.toml")

        backend = ChannelConfigBackend(cache_file=cache_path)
        for name in ["H1:TEST-CHANNEL_A", "H1:TEST-CHANNEL_B"]:
            assert name in backend.metadata

        assert len(backend.partition_metadata) == 2
        assert backend.partition_metadata["existing_partition"]._next_index == 5
        assert backend.partition_metadata["existing_partition2"]._next_index == 42


def test_channel_config_backend_save_metadata():
    with TemporaryDirectory() as cache_dir:
        cache_path = setup_metadata(cache_dir, "metadata_test_empty.toml")
        backend = ChannelConfigBackend(cache_file=cache_path)
        assert backend.cache_file is not None

        channels = [
            create_channel(
                "H1:TEST-CHANNEL_A",
                publisher="test_publisher",
                partition_id="existing_partition",
            ),
            create_channel(
                "H1:TEST-CHANNEL_B",
                publisher="test_publisher",
                partition_id="existing_partition2",
            ),
        ]
        partition_metadata: dict[str, PartitionMetadata] = {}
        channels = partition_channels(
            channels, publisher="test_publisher", partition_metadata=partition_metadata
        )
        backend.update(channels, partition_metadata)

        with open(cache_path, "rt") as f:
            objs = toml.load(f)
            assert (
                objs["__internal_partition_metadata"]["existing_partition"][
                    "next_index"
                ]
                == 1
            )
            assert (
                objs["__internal_partition_metadata"]["existing_partition"][
                    "next_index"
                ]
                == 1
            )


def test_load_publisher_info():
    """Test that [publisher] section is parsed into PublisherInfo."""
    backend = ChannelConfigBackend()
    backend.load(
        Path(__file__).parent / "data" / "metadata_test_publisher.toml",
    )

    info = backend.get_publisher_info("static_publisher")
    assert info is not None
    assert info.publisher_id == "static_publisher"
    assert info.stride == 1_000_000_000
    assert info.max_latency == 12_000_000_000
    assert info.dynamic is False


def test_load_publisher_info_dynamic():
    """Test that dynamic=true is parsed correctly."""
    backend = ChannelConfigBackend()
    backend.load(
        Path(__file__).parent / "data" / "metadata_test_publisher_dynamic.toml",
    )

    info = backend.get_publisher_info("test_publisher")
    assert info is not None
    assert info.dynamic is True


def test_publisher_info_unknown():
    """get_publisher_info returns None for unknown publisher."""
    backend = ChannelConfigBackend()
    assert backend.get_publisher_info("nonexistent") is None


def test_publisher_fields_applied_to_channels():
    """Channels inherit stride, max_latency, publisher from [publisher] section."""
    backend = ChannelConfigBackend()
    channels = backend.load(
        Path(__file__).parent / "data" / "metadata_test_publisher_dynamic.toml",
    )

    for channel in channels:
        assert channel.stride == 1_000_000_000
        assert channel.max_latency == 12_000_000_000
        assert channel.publisher == "test_publisher"


def test_load_without_publisher_section():
    """Loading a TOML without [publisher] creates no PublisherInfo."""
    backend = ChannelConfigBackend()
    backend.load(
        Path(__file__).parent / "data" / "metadata_test_1.toml",
    )

    assert len(backend.publisher_info) == 0


def test_dynamic_publisher_reconciles_cached_channels():
    """Loading a dynamic publisher config updates stale cached channels."""
    with TemporaryDirectory() as cache_dir:
        # load cache with stale stride/max_latency values
        cache_path = setup_metadata(cache_dir, "metadata_test_cache_stale.toml")
        backend = ChannelConfigBackend(cache_file=cache_path)

        # verify stale values loaded from cache
        for name in ["H1:TEST-CHANNEL_CACHED_A", "H1:TEST-CHANNEL_CACHED_B"]:
            assert backend.metadata[name].stride == 500_000_000
            assert backend.metadata[name].max_latency == 6_000_000_000

        # load the dynamic publisher config (has stride=1e9, max_latency=12e9)
        backend.load(
            Path(__file__).parent / "data" / "metadata_test_publisher_dynamic.toml",
            overwrite=True,
        )

        # cached channels should now have reconciled values
        for name in ["H1:TEST-CHANNEL_CACHED_A", "H1:TEST-CHANNEL_CACHED_B"]:
            assert backend.metadata[name].stride == 1_000_000_000
            assert backend.metadata[name].max_latency == 12_000_000_000

        # the config's own channels should also be present
        for name in ["H1:TEST-CHANNEL_A", "H1:TEST-CHANNEL_B"]:
            assert name in backend.metadata
            assert backend.metadata[name].stride == 1_000_000_000
            assert backend.metadata[name].max_latency == 12_000_000_000
