"""
Copyright (C) 2025 Luciano Guerche

This file is part of rabbitmq-mcp-server.

rabbitmq-mcp-server is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

rabbitmq-mcp-server is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with rabbitmq-mcp-server. If not, see <https://www.gnu.org/licenses/>.
"""

from pathlib import Path

import pytest
from pydantic import SecretStr, ValidationError

from config.settings import Settings


class TestSettingsFields:
    """Test Settings class field definitions and defaults (AC #2)."""

    def test_amqp_defaults(self):
        """Test AMQP configuration has correct defaults."""
        settings = Settings()
        assert settings.amqp_host == "localhost"
        assert settings.amqp_port == 5672
        assert settings.amqp_user == "guest"
        assert settings.amqp_password.get_secret_value() == "guest"
        assert settings.amqp_vhost == "/"
        assert settings.amqp_use_tls is False
        assert settings.amqp_verify_cert is True
        assert settings.amqp_ca_bundle is None

    def test_http_defaults(self):
        """Test HTTP configuration has correct defaults."""
        settings = Settings()
        assert settings.http_host == "localhost"
        assert settings.http_port == 15672
        assert settings.http_user == "guest"
        assert settings.http_password.get_secret_value() == "guest"
        assert settings.http_use_tls is False
        assert settings.http_verify_cert is True
        assert settings.http_ca_bundle is None
        assert settings.max_http_connections == 5

    def test_general_defaults(self):
        """Test general configuration has correct defaults."""
        settings = Settings()
        assert settings.config_file is None
        assert settings.health_check_interval == 30
        assert settings.reconnect_max_delay == 60
        assert settings.log_level == "INFO"

    def test_secretstr_protection_amqp(self):
        """Test AMQP password uses SecretStr (AC #4)."""
        settings = Settings(amqp_password="secret123")
        assert isinstance(settings.amqp_password, SecretStr)
        # Verify password is not exposed in string representation
        settings_str = str(settings)
        assert "secret123" not in settings_str
        # Should be able to retrieve actual value with get_secret_value()
        assert settings.amqp_password.get_secret_value() == "secret123"

    def test_secretstr_protection_http(self):
        """Test HTTP password uses SecretStr (AC #4)."""
        settings = Settings(http_password="secret456")
        assert isinstance(settings.http_password, SecretStr)
        settings_str = str(settings)
        assert "secret456" not in settings_str
        assert settings.http_password.get_secret_value() == "secret456"

    def test_secretstr_protection_rabbitmq_password(self):
        """Test legacy rabbitmq_password uses SecretStr (AC #4)."""
        settings = Settings(rabbitmq_password="secret789")
        assert isinstance(settings.rabbitmq_password, SecretStr)
        settings_str = str(settings)
        assert "secret789" not in settings_str


class TestSettingsValidation:
    """Test Settings validation rules (AC #5, #6)."""

    def test_port_validation_valid(self):
        """Test valid port ranges are accepted."""
        settings = Settings(amqp_port=5672, http_port=15672)
        assert settings.amqp_port == 5672
        assert settings.http_port == 15672

        # Test edge cases
        settings_min = Settings(amqp_port=1, http_port=1)
        assert settings_min.amqp_port == 1

        settings_max = Settings(amqp_port=65535, http_port=65535)
        assert settings_max.amqp_port == 65535

    def test_port_validation_invalid_low(self):
        """Test ports below 1 are rejected."""
        with pytest.raises(ValidationError) as exc_info:
            Settings(amqp_port=0)
        assert "Port must be between 1 and 65535" in str(exc_info.value)

    def test_port_validation_invalid_high(self):
        """Test ports above 65535 are rejected."""
        with pytest.raises(ValidationError) as exc_info:
            Settings(amqp_port=65536)
        assert "Port must be between 1 and 65535" in str(exc_info.value)

    def test_vhost_validation_valid(self):
        """Test valid vhost formats are accepted."""
        settings = Settings(amqp_vhost="/")
        assert settings.amqp_vhost == "/"

        settings2 = Settings(amqp_vhost="/test")
        assert settings2.amqp_vhost == "/test"

        settings3 = Settings(amqp_vhost="/prod/app1")
        assert settings3.amqp_vhost == "/prod/app1"

    def test_vhost_validation_invalid(self):
        """Test vhost without leading slash is rejected (AC #5)."""
        with pytest.raises(ValidationError) as exc_info:
            Settings(amqp_vhost="test")
        assert "vhost must start with '/'" in str(exc_info.value)

    def test_ca_bundle_validation_missing_file(self, tmp_path):
        """Test CA bundle validation fails for non-existent files."""
        non_existent = tmp_path / "missing.pem"
        with pytest.raises(ValidationError) as exc_info:
            Settings(amqp_ca_bundle=str(non_existent))
        assert "CA bundle file not found" in str(exc_info.value)

    def test_ca_bundle_validation_existing_file(self, tmp_path):
        """Test CA bundle validation passes for existing files."""
        ca_file = tmp_path / "ca-bundle.pem"
        ca_file.write_text("FAKE CA BUNDLE")
        settings = Settings(amqp_ca_bundle=str(ca_file))
        assert settings.amqp_ca_bundle == str(ca_file)

    def test_ca_bundle_none_is_valid(self):
        """Test CA bundle can be None."""
        settings = Settings(amqp_ca_bundle=None)
        assert settings.amqp_ca_bundle is None

    def test_tls_consistency_warning(self, capsys):
        """Test TLS consistency validation warns when needed."""
        Settings(amqp_use_tls=True, amqp_verify_cert=False)
        captured = capsys.readouterr()
        assert (
            "Warning: AMQP TLS enabled but certificate verification disabled"
            in captured.out
        )


class TestSettingsEnvironmentVariables:
    """Test Settings loads from environment variables (AC #1, #2)."""

    def test_amqp_env_vars(self, monkeypatch):
        """Test AMQP configuration from environment variables."""
        monkeypatch.setenv("AMQP_HOST", "rabbitmq.example.com")
        monkeypatch.setenv("AMQP_PORT", "5671")
        monkeypatch.setenv("AMQP_USER", "admin")
        monkeypatch.setenv("AMQP_PASSWORD", "secret")
        monkeypatch.setenv("AMQP_VHOST", "/production")
        monkeypatch.setenv("AMQP_USE_TLS", "true")

        settings = Settings()
        assert settings.amqp_host == "rabbitmq.example.com"
        assert settings.amqp_port == 5671
        assert settings.amqp_user == "admin"
        assert settings.amqp_password.get_secret_value() == "secret"
        assert settings.amqp_vhost == "/production"
        assert settings.amqp_use_tls is True

    def test_http_env_vars(self, monkeypatch):
        """Test HTTP configuration from environment variables."""
        monkeypatch.setenv("HTTP_HOST", "api.example.com")
        monkeypatch.setenv("HTTP_PORT", "15671")
        monkeypatch.setenv("HTTP_USER", "api_user")
        monkeypatch.setenv("HTTP_PASSWORD", "api_secret")
        monkeypatch.setenv("MAX_HTTP_CONNECTIONS", "10")

        settings = Settings()
        assert settings.http_host == "api.example.com"
        assert settings.http_port == 15671
        assert settings.http_user == "api_user"
        assert settings.http_password.get_secret_value() == "api_secret"
        assert settings.max_http_connections == 10


class TestSettingsConstructor:
    """Test Settings can be constructed with explicit values."""

    def test_construct_with_kwargs(self):
        """Test Settings can be constructed with keyword arguments."""
        settings = Settings(
            amqp_host="custom.host",
            amqp_port=9999,
            amqp_user="custom_user",
            amqp_password="custom_pass",
            http_host="http.host",
            http_port=8888,
        )
        assert settings.amqp_host == "custom.host"
        assert settings.amqp_port == 9999
        assert settings.amqp_user == "custom_user"
        assert settings.amqp_password.get_secret_value() == "custom_pass"
        assert settings.http_host == "http.host"
        assert settings.http_port == 8888


class TestReconnectionSettings:
    """Test reconnection configuration settings (Story 2.5)."""

    def test_reconnect_max_delay_default(self):
        """Test reconnect_max_delay has correct default (AC #1)."""
        settings = Settings()
        assert settings.reconnect_max_delay == 60

    def test_reconnect_reset_after_default(self):
        """Test reconnect_reset_after has correct default (AC #9)."""
        settings = Settings()
        assert settings.reconnect_reset_after == 300

    def test_reconnect_max_delay_valid_range(self):
        """Test reconnect_max_delay accepts valid range."""
        settings_min = Settings(reconnect_max_delay=10)
        assert settings_min.reconnect_max_delay == 10

        settings_max = Settings(reconnect_max_delay=300)
        assert settings_max.reconnect_max_delay == 300

        settings_mid = Settings(reconnect_max_delay=60)
        assert settings_mid.reconnect_max_delay == 60

    def test_reconnect_max_delay_invalid_too_low(self):
        """Test reconnect_max_delay rejects values below minimum."""
        with pytest.raises(ValidationError) as exc_info:
            Settings(reconnect_max_delay=5)

        assert "between 10 and 300" in str(exc_info.value).lower()

    def test_reconnect_max_delay_invalid_too_high(self):
        """Test reconnect_max_delay rejects values above maximum."""
        with pytest.raises(ValidationError) as exc_info:
            Settings(reconnect_max_delay=500)

        assert "between 10 and 300" in str(exc_info.value).lower()

    def test_reconnect_reset_after_valid_range(self):
        """Test reconnect_reset_after accepts valid range."""
        settings_min = Settings(reconnect_reset_after=60)
        assert settings_min.reconnect_reset_after == 60

        settings_max = Settings(reconnect_reset_after=3600)
        assert settings_max.reconnect_reset_after == 3600

        settings_mid = Settings(reconnect_reset_after=300)
        assert settings_mid.reconnect_reset_after == 300

    def test_reconnect_reset_after_invalid_too_low(self):
        """Test reconnect_reset_after rejects values below minimum."""
        with pytest.raises(ValidationError) as exc_info:
            Settings(reconnect_reset_after=30)

        assert "between 60 and 3600" in str(exc_info.value).lower()

    def test_reconnect_reset_after_invalid_too_high(self):
        """Test reconnect_reset_after rejects values above maximum."""
        with pytest.raises(ValidationError) as exc_info:
            Settings(reconnect_reset_after=4000)

        assert "between 60 and 3600" in str(exc_info.value).lower()

    def test_reconnect_settings_custom_values(self):
        """Test setting both reconnection settings with custom values."""
        settings = Settings(
            reconnect_max_delay=120,
            reconnect_reset_after=600,
        )
        assert settings.reconnect_max_delay == 120
        assert settings.reconnect_reset_after == 600
