"""
This module contains unit tests for the Configurator class, specifically
testing its ability to read and initialize bus configurations from various sources.
"""

import os
import tempfile

import pytest

from kurier.configurations.configurator import Configurator
from kurier.hermes import Hermes
from kurier.utilities.serializers import CloudEventSerializer

Hermes.is_enabled = False

CONFIGS_DIR = "tests/resources/configs"


class TestEnvVarInterpolation:
    """Tests for environment variable interpolation in config strings."""

    def test_interpolate_env_var(self, monkeypatch):
        monkeypatch.setenv("METRO_HOST", "broker.example.com")
        result = Configurator._interpolate_env_vars("host = ${METRO_HOST}")
        assert result == "host = broker.example.com"

    def test_interpolate_env_var_with_default(self):
        result = Configurator._interpolate_env_vars("host = ${UNDEFINED_VAR:-localhost}")
        assert result == "host = localhost"

    def test_interpolate_env_var_set_ignores_default(self, monkeypatch):
        monkeypatch.setenv("METRO_HOST", "broker.example.com")
        result = Configurator._interpolate_env_vars("host = ${METRO_HOST:-localhost}")
        assert result == "host = broker.example.com"

    def test_interpolate_unset_var_no_default_raises(self):
        with pytest.raises(ValueError, match="Unresolved environment variables"):
            Configurator._interpolate_env_vars("host = ${TOTALLY_UNDEFINED_VAR_XYZ}")

    def test_interpolate_multiple_vars(self, monkeypatch):
        monkeypatch.setenv("METRO_USER", "admin")
        monkeypatch.setenv("METRO_PASS", "secret")
        result = Configurator._interpolate_env_vars("username = ${METRO_USER}\npassword = ${METRO_PASS}")
        assert "admin" in result
        assert "secret" in result

    def test_interpolate_empty_default(self):
        result = Configurator._interpolate_env_vars("key = ${MISSING_VAR:-}")
        assert result == "key = "

    def test_no_interpolation_without_braces(self):
        result = Configurator._interpolate_env_vars("host = $METRO_HOST")
        assert result == "host = $METRO_HOST"

    @pytest.mark.asyncio
    async def test_interpolation_in_full_config(self, monkeypatch):
        monkeypatch.setenv("BUS_NAME", "my_memory_bus")

        config_content = """
        [in_memory]
        name = "${BUS_NAME}"
        """

        buses = await Configurator.init_transports(config_content)
        assert len(buses) == 1
        assert "my_memory_bus" in buses


class TestConfiguratorBasic:
    """Basic tests for Configurator functionality."""

    @pytest.mark.asyncio
    async def test_read_config_source(self):
        """
        Tests the `init_transports` method of the Configurator class for reading
        and initializing bus configurations.

        It verifies:
        1. That a `ValueError` is raised when attempting to read from a non-existent file.
        2. That the correct number of buses are initialized from a valid TOML configuration file.
        """
        with pytest.raises(ValueError):
            await Configurator.init_transports(os.path.join(CONFIGS_DIR, "non_existent_file.txt"))

        buses = await Configurator.init_transports(os.path.join(CONFIGS_DIR, "test_config.toml"))
        assert len(buses) == 2

    @pytest.mark.asyncio
    async def test_read_config_with_invalid_path(self):
        """Test that invalid file paths raise appropriate errors."""
        with pytest.raises(ValueError):
            await Configurator.init_transports("/invalid/path/config.toml")

        with pytest.raises(ValueError):
            await Configurator.init_transports("")

        with pytest.raises(ValueError):
            await Configurator.init_transports(None)

    @pytest.mark.asyncio
    async def test_read_config_with_different_extensions(self):
        """Test reading configuration files with different extensions."""
        # Test with .toml extension
        if os.path.exists(os.path.join(CONFIGS_DIR, "test_config.toml")):
            buses = await Configurator.init_transports(os.path.join(CONFIGS_DIR, "test_config.toml"))
            assert isinstance(buses, dict)

        # Test with unsupported extension
        with pytest.raises(ValueError):
            await Configurator.init_transports(os.path.join(CONFIGS_DIR, "test_config.json"))


class TestConfiguratorAdvanced:
    """Advanced tests for Configurator functionality."""

    @pytest.mark.asyncio
    async def test_read_empty_config_file(self):
        """Test reading an empty configuration file."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
            f.write("")
            temp_path = f.name

        try:
            buses = await Configurator.init_transports(temp_path)
            assert buses == {}
        finally:
            os.unlink(temp_path)

    @pytest.mark.asyncio
    async def test_read_malformed_toml_file(self):
        """Test reading a malformed TOML file."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
            f.write("invalid toml [content")
            temp_path = f.name

        try:
            with pytest.raises(ValueError):
                await Configurator.init_transports(temp_path)
        finally:
            os.unlink(temp_path)

    @pytest.mark.asyncio
    async def test_read_config_with_single_bus(self):
        """Test reading configuration with a single bus configuration."""
        config_content = """
        [rabbitmq]
        name = rabbitmq
        host = localhost
        port = 5672
        username = guest
        password = guest
        virtual_host = /
        """

        with tempfile.NamedTemporaryFile(mode="w", suffix=".cfg", delete=False) as f:
            f.write(config_content)
            temp_path = f.name

        try:
            buses = await Configurator.init_transports(temp_path)
            assert len(buses) == 1
        finally:
            os.unlink(temp_path)

    @pytest.mark.asyncio
    async def test_read_config_with_multiple_buses(self):
        """Test reading configuration with multiple bus configurations."""
        config_content = """
        [rabbitmq]
        [rabbitmq.test_bus]
        name = "test_bus"
        host = "localhost"
        port = 5672
        username = "guest"
        password = "guest"
        virtual_host = "/test"

        [rabbitmq.my_bus]
        name = "my_bus"
        host = "localhost"
        port = 5672
        username = "guest"
        password = "guest"
        virtual_host = "/test"
        """

        with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
            f.write(config_content)
            temp_path = f.name

        try:
            buses = await Configurator.init_transports(temp_path)
            assert len(buses) == 2
        finally:
            os.unlink(temp_path)

    @pytest.mark.asyncio
    async def test_read_config_with_complex_configuration(self):
        """Test reading configuration with complex bus settings."""
        config_content = """
        [rabbitmq]
        name = "test_bus"
        host = "localhost"
        port = 5672
        username = "guest"
        password = "guest"
        virtual_host = "/test"

        [azure]
        name = "my_bus"
        connection_string = "Endpoint=sb://localhost;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;"
        """

        with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
            f.write(config_content)
            temp_path = f.name

        try:
            buses = await Configurator.init_transports(temp_path)
            assert len(buses) == 2
        finally:
            os.unlink(temp_path)

    @pytest.mark.asyncio
    async def test_inmemory_cloudevents_serializer_configuration(self):
        config_content = """
        [in_memory]
        name = "cloud_bus"
        serializer = "cloudevents"
        cloudevents_source = "kurier://cloud-bus"
        """

        buses = await Configurator.init_transports(config_content)
        line = buses["cloud_bus"]

        assert isinstance(line.serializer, CloudEventSerializer)
        assert line.serializer.source == "kurier://cloud-bus"

        await line.close()

    @pytest.mark.asyncio
    async def test_read_config_with_invalid_bus_type(self):
        """Test reading configuration with invalid bus type."""
        config_content = """
        [[bus]]
        type = "invalid_bus_type"
        name = "test_bus"
        """

        with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
            f.write(config_content)
            temp_path = f.name

        try:
            with pytest.raises(ValueError):
                await Configurator.init_transports(temp_path)
        finally:
            os.unlink(temp_path)

    @pytest.mark.asyncio
    async def test_read_config_with_missing_required_fields(self):
        """Test reading configuration with missing required fields."""
        config_content = """
        [rabbitmq]
        name = rabbitmq
        host = localhost
        port = 5672
        virtual_host = /
        # Missing username and password - required fields
        """

        with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
            f.write(config_content)
            temp_path = f.name

        try:
            # The configurator should handle validation errors gracefully
            # and return an empty result or skip invalid configurations
            buses = await Configurator.init_transports(temp_path)
            # Expect that the invalid bus configuration is not included
            assert len(buses) == 0 or "rabbitmq" not in buses
        finally:
            os.unlink(temp_path)

    @pytest.mark.asyncio
    async def test_file_permissions_error(self):
        """Test handling of file permission errors."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
            f.write("[[bus]]\ntype = 'in_memory'\nname = 'test'")
            temp_path = f.name

        try:
            # Remove read permissions
            os.chmod(temp_path, 0o000)

            with pytest.raises(PermissionError):
                await Configurator.init_transports(temp_path)
        finally:
            # Restore permissions and clean up
            os.chmod(temp_path, 0o644)
            os.unlink(temp_path)

    @pytest.mark.asyncio
    async def test_concurrent_config_loading(self):
        """Test loading configuration concurrently. Requires a local RabbitMQ broker."""
        import asyncio
        import socket

        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            reachable = sock.connect_ex(("localhost", 5672)) == 0
        finally:
            sock.close()
        if not reachable:
            pytest.skip("RabbitMQ broker not available on localhost:5672")

        config_content = """
        [rabbitmq]
        name = rabbitmq
        host = localhost
        port = 5672
        username = guest
        password = guest
        virtual_host = /
        """

        with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
            f.write(config_content)
            temp_path = f.name

        try:
            # Load config multiple times concurrently
            tasks = [Configurator.init_transports(temp_path) for _ in range(5)]

            results = await asyncio.gather(*tasks)

            # All should succeed and return the same result
            for buses in results:
                assert len(buses) == 1
        finally:
            os.unlink(temp_path)
