"""Tests for CloudFormation template generator."""

import pytest
from awslabs.cloudwatch_mcp_server.cloudwatch_metrics.cloudformation_template_generator import (
    CloudFormationTemplateGenerator,
)
from awslabs.cloudwatch_mcp_server.cloudwatch_metrics.constants import COMPARISON_OPERATOR_ANOMALY


class MockThreshold:
    """Mock threshold object for testing."""

    def __init__(self, sensitivity=2):
        """Initialize mock threshold."""
        self.sensitivity = sensitivity


class TestCloudFormationTemplateGenerator:
    """Test CloudFormation template generation."""

    @pytest.fixture
    def generator(self):
        """Create a CloudFormationTemplateGenerator instance."""
        return CloudFormationTemplateGenerator()

    def test_format_alarm_data_with_anomaly_threshold(self, generator):
        """Test processing alarm data with anomaly detection threshold."""
        alarm_data = {
            'threshold': {'sensitivity': 3},
            'comparisonOperator': COMPARISON_OPERATOR_ANOMALY,
            'alarmDescription': 'Test alarm',
        }

        result = generator._format_anomaly_detection_alarm_data(alarm_data)

        assert result['sensitivity'] == 3
        assert result['alarmDescription'] == 'Test alarm'

    def test_format_alarm_data_defaults(self, generator):
        """Test processing alarm data applies defaults."""
        alarm_data = {
            'threshold': {'sensitivity': 2},
            'comparisonOperator': COMPARISON_OPERATOR_ANOMALY,
        }

        result = generator._format_anomaly_detection_alarm_data(alarm_data)

        assert result['alarmDescription'] == 'CloudWatch Alarm generated by CloudWatch MCP server.'
        assert result['statistic'] == 'Average'
        assert result['sensitivity'] == 2

    def test_generate_metric_alarm_template_non_anomaly_returns_empty(self, generator):
        """Test that non-anomaly detection alarms return empty template."""
        alarm_data = {
            'comparisonOperator': 'GreaterThanThreshold'  # Not anomaly detection
        }

        result = generator.generate_metric_alarm_template(alarm_data)

        assert result == {}

    def test_is_anomaly_detection_alarm(self, generator):
        """Test anomaly detection alarm identification."""
        anomaly_alarm = {'comparisonOperator': COMPARISON_OPERATOR_ANOMALY}
        static_alarm = {'comparisonOperator': 'GreaterThanThreshold'}

        assert generator._is_anomaly_detection_alarm(anomaly_alarm) is True
        assert generator._is_anomaly_detection_alarm(static_alarm) is False

    def test_special_characters_in_metric_names_are_escaped(self, generator):
        """Test that special characters in metric names are properly handled in JSON serialization."""
        import json

        alarm_data = {
            'metricName': 'Metric"With"Quotes',
            'namespace': 'Namespace\\With\\Backslash',
            'alarmDescription': 'Test\nWith\nNewlines',
            'dimensions': [
                {'Name': 'Dimension"Key', 'Value': 'Value"With"Quotes'},
                {'Name': 'Key\\With\\Backslash', 'Value': 'Normal'},
            ],
            'threshold': {'sensitivity': 2},
            'comparisonOperator': COMPARISON_OPERATOR_ANOMALY,
            'statistic': 'Average',
            'period': 300,
        }

        result = generator.generate_metric_alarm_template(alarm_data)

        # Serialize to JSON string (this is what happens when MCP returns the response)
        template_json = json.dumps(result)

        # Verify the JSON string contains properly escaped characters
        assert '\\"' in template_json  # Quotes should be escaped as \"
        assert '\\\\' in template_json  # Backslashes should be escaped as \\
        assert '\\n' in template_json  # Newlines should be escaped as \n

        # Verify it can be parsed back
        parsed = json.loads(template_json)
        assert 'Resources' in parsed

        # Find resources
        alarm_resource = None
        detector_resource = None
        for key, resource in parsed['Resources'].items():
            if resource['Type'] == 'AWS::CloudWatch::Alarm':
                alarm_resource = resource
            elif resource['Type'] == 'AWS::CloudWatch::AnomalyDetector':
                detector_resource = resource

        # Verify values are preserved after round-trip
        assert detector_resource is not None
        assert alarm_resource is not None
        assert detector_resource['Properties']['MetricName'] == 'Metric"With"Quotes'
        assert detector_resource['Properties']['Namespace'] == 'Namespace\\With\\Backslash'
        assert alarm_resource['Properties']['AlarmDescription'] == 'Test\nWith\nNewlines'

    def test_alarm_name_not_required(self, generator):
        """Test that alarmName is not required in the template."""
        alarm_data = {
            'metricName': 'CPUUtilization',
            'namespace': 'AWS/EC2',
            'threshold': {'sensitivity': 2},
            'comparisonOperator': COMPARISON_OPERATOR_ANOMALY,
        }

        result = generator.generate_metric_alarm_template(alarm_data)

        # Should generate successfully without alarmName
        assert result is not None
        assert 'Resources' in result

        # Verify alarm doesn't have AlarmName property
        for resource in result['Resources'].values():
            if resource['Type'] == 'AWS::CloudWatch::Alarm':
                assert 'AlarmName' not in resource['Properties']

    def test_missing_metric_name_raises_error(self, generator):
        """Test that missing metricName raises ValueError."""
        alarm_data = {
            'namespace': 'AWS/EC2',
            'threshold': {'sensitivity': 2},
            'comparisonOperator': COMPARISON_OPERATOR_ANOMALY,
        }

        with pytest.raises(ValueError, match='Metric Name is required'):
            generator.generate_metric_alarm_template(alarm_data)

    def test_missing_namespace_raises_error(self, generator):
        """Test that missing namespace raises ValueError."""
        alarm_data = {
            'metricName': 'CPUUtilization',
            'threshold': {'sensitivity': 2},
            'comparisonOperator': COMPARISON_OPERATOR_ANOMALY,
        }

        with pytest.raises(ValueError, match='Metric Namespace is required'):
            generator.generate_metric_alarm_template(alarm_data)

    def test_empty_metric_name_raises_error(self, generator):
        """Test that empty metricName raises ValueError."""
        alarm_data = {
            'metricName': '',
            'namespace': 'AWS/EC2',
            'threshold': {'sensitivity': 2},
            'comparisonOperator': COMPARISON_OPERATOR_ANOMALY,
        }

        with pytest.raises(ValueError, match='Metric Name is required'):
            generator.generate_metric_alarm_template(alarm_data)

    def test_dimensions_optional(self, generator):
        """Test that dimensions are optional."""
        alarm_data = {
            'metricName': 'CPUUtilization',
            'namespace': 'AWS/EC2',
            'threshold': {'sensitivity': 2},
            'comparisonOperator': COMPARISON_OPERATOR_ANOMALY,
        }

        result = generator.generate_metric_alarm_template(alarm_data)

        # Should succeed without dimensions
        assert result is not None
        assert 'Resources' in result

    def test_sanitize_resource_name_empty_string(self, generator):
        """Test sanitization of empty string resource name."""
        result = generator._sanitize_resource_name('')
        assert result == 'Resource'
        assert result[0].isalpha()

    def test_sanitize_resource_name_starts_with_number(self, generator):
        """Test sanitization when name starts with number."""
        result = generator._sanitize_resource_name('123Test')
        assert result.startswith('Resource')
        assert result[0].isalpha()

    def test_sanitize_resource_name_long_string(self, generator):
        """Test sanitization truncates long strings."""
        long_name = 'A' * 300
        result = generator._sanitize_resource_name(long_name)
        assert len(result) == 255
