"""
Unit tests for S3ConnectionConfig class.

This module tests the AWS S3-specific cloud storage connection configuration,
including bucket name validation, profile name validation, and region name validation.
"""

import unittest

from parameterized import parameterized
from pydantic import ValidationError

from data_exchange_agent.config.sections.connections.cloud_storages.s3 import S3ConnectionConfig


TEST_BUCKET = "my-test-bucket"
TEST_PROFILE = "test-profile"
TEST_REGION = "us-east-1"


class TestS3ConnectionConfigInitialization(unittest.TestCase):
    """Test suite for S3ConnectionConfig initialization."""

    def test_initialization_with_all_fields(self):
        """Test S3ConnectionConfig initialization with all fields."""
        config = S3ConnectionConfig(
            bucket_name=TEST_BUCKET,
            profile_name=TEST_PROFILE,
            region_name=TEST_REGION,
        )

        self.assertEqual(config.bucket_name, TEST_BUCKET)
        self.assertEqual(config.profile_name, TEST_PROFILE)
        self.assertEqual(config.region_name, TEST_REGION)

    def test_initialization_without_region(self):
        """Test S3ConnectionConfig initialization without region_name."""
        config = S3ConnectionConfig(
            bucket_name=TEST_BUCKET,
            profile_name=TEST_PROFILE,
        )

        self.assertEqual(config.bucket_name, TEST_BUCKET)
        self.assertEqual(config.profile_name, TEST_PROFILE)
        self.assertIsNone(config.region_name)

    def test_region_name_defaults_to_none(self):
        """Test that region_name defaults to None."""
        config = S3ConnectionConfig(
            bucket_name=TEST_BUCKET,
            profile_name=TEST_PROFILE,
        )

        self.assertIsNone(config.region_name)


class TestS3ConnectionConfigRepr(unittest.TestCase):
    """Test suite for S3ConnectionConfig __repr__ method."""

    def test_repr_with_all_fields(self):
        """Test __repr__ includes all fields."""
        config = S3ConnectionConfig(
            bucket_name=TEST_BUCKET,
            profile_name=TEST_PROFILE,
            region_name=TEST_REGION,
        )

        repr_str = repr(config)

        self.assertIn("S3ConnectionConfig", repr_str)
        self.assertIn(f"bucket_name='{TEST_BUCKET}'", repr_str)
        self.assertIn(f"profile_name='{TEST_PROFILE}'", repr_str)
        self.assertIn(f"region_name='{TEST_REGION}'", repr_str)

    def test_repr_with_none_region(self):
        """Test __repr__ handles None region_name."""
        config = S3ConnectionConfig(
            bucket_name=TEST_BUCKET,
            profile_name=TEST_PROFILE,
        )

        repr_str = repr(config)

        self.assertIn("region_name='None'", repr_str)


class TestS3ConnectionConfigRequiredFields(unittest.TestCase):
    """Test suite for required fields validation."""

    def test_missing_bucket_name_raises_error(self):
        """Test that missing bucket_name raises ValidationError."""
        with self.assertRaises(ValidationError) as context:
            S3ConnectionConfig(
                profile_name=TEST_PROFILE,
            )

        errors = context.exception.errors()
        self.assertTrue(any("bucket_name" in str(e["loc"]) for e in errors))

    def test_missing_profile_name_raises_error(self):
        """Test that missing profile_name raises ValidationError."""
        with self.assertRaises(ValidationError) as context:
            S3ConnectionConfig(
                bucket_name=TEST_BUCKET,
            )

        errors = context.exception.errors()
        self.assertTrue(any("profile_name" in str(e["loc"]) for e in errors))


class TestS3ConnectionConfigBucketNameValidation(unittest.TestCase):
    """Test suite for bucket name validation."""

    @parameterized.expand(
        [
            ("minimum_length", "abc"),  # Minimum 3 characters
            ("maximum_length", "a" * 63),  # Maximum 63 characters
            ("lowercase_letters", "mybucket"),
            ("numbers_only", "123456789"),
            ("lowercase_and_numbers", "my-bucket-123"),
            ("with_dots", "my.bucket.name"),
            ("with_hyphens", "my-bucket-name"),
            ("starts_with_number", "123mybucket"),
            ("ends_with_number", "mybucket123"),
            ("complex_valid", "my.bucket-name.123"),
        ]
    )
    def test_valid_bucket_names(self, name, bucket_name):
        """Test valid bucket names are accepted."""
        config = S3ConnectionConfig(
            bucket_name=bucket_name,
            profile_name=TEST_PROFILE,
        )
        self.assertEqual(config.bucket_name, bucket_name)

    def test_bucket_name_too_short_raises_error(self):
        """Test bucket name shorter than 3 characters raises error."""
        with self.assertRaises(ValidationError) as context:
            S3ConnectionConfig(
                bucket_name="ab",
                profile_name=TEST_PROFILE,
            )

        self.assertIn("Bucket name length must be between 3 and 63 characters", str(context.exception))

    def test_bucket_name_too_long_raises_error(self):
        """Test bucket name longer than 63 characters raises error."""
        with self.assertRaises(ValidationError) as context:
            S3ConnectionConfig(
                bucket_name="a" * 64,
                profile_name=TEST_PROFILE,
            )

        self.assertIn("Bucket name length must be between 3 and 63 characters", str(context.exception))

    @parameterized.expand(
        [
            ("uppercase_letters", "MyBucket"),
            ("starts_with_hyphen", "-mybucket"),
            ("ends_with_hyphen", "mybucket-"),
            ("starts_with_dot", ".mybucket"),
            ("ends_with_dot", "mybucket."),
            ("underscore", "my_bucket"),
            ("special_chars", "my@bucket"),
            ("space", "my bucket"),
        ]
    )
    def test_invalid_bucket_name_pattern_raises_error(self, name, bucket_name):
        """Test invalid bucket name patterns raise error."""
        with self.assertRaises(ValidationError) as context:
            S3ConnectionConfig(
                bucket_name=bucket_name,
                profile_name=TEST_PROFILE,
            )

        self.assertIn(
            "Bucket name must start and end with a lowercase letter or number",
            str(context.exception),
        )

    @parameterized.expand(
        [
            ("simple_ip", "192.168.1.1"),
            ("different_ip", "10.0.0.1"),
            ("another_ip", "172.16.0.1"),
        ]
    )
    def test_bucket_name_as_ip_address_raises_error(self, name, bucket_name):
        """Test bucket name formatted as IP address raises error."""
        with self.assertRaises(ValidationError) as context:
            S3ConnectionConfig(
                bucket_name=bucket_name,
                profile_name=TEST_PROFILE,
            )

        self.assertIn("Bucket name cannot be formatted as an IP address", str(context.exception))

    def test_bucket_name_with_consecutive_periods_raises_error(self):
        """Test bucket name with consecutive periods raises error."""
        with self.assertRaises(ValidationError) as context:
            S3ConnectionConfig(
                bucket_name="my..bucket",
                profile_name=TEST_PROFILE,
            )

        self.assertIn("Bucket name cannot contain two adjacent periods", str(context.exception))

    @parameterized.expand(
        [
            ("dot_hyphen", "my.-bucket"),
            ("hyphen_dot", "my-.bucket"),
        ]
    )
    def test_bucket_name_with_dot_adjacent_to_hyphen_raises_error(self, name, bucket_name):
        """Test bucket name with dot adjacent to hyphen raises error."""
        with self.assertRaises(ValidationError) as context:
            S3ConnectionConfig(
                bucket_name=bucket_name,
                profile_name=TEST_PROFILE,
            )

        self.assertIn("Bucket name cannot have a dot adjacent to a hyphen", str(context.exception))


class TestS3ConnectionConfigProfileNameValidation(unittest.TestCase):
    """Test suite for profile name validation."""

    @parameterized.expand(
        [
            ("simple_name", "default"),
            ("with_numbers", "profile123"),
            ("with_hyphen", "my-profile"),
            ("with_underscore", "my_profile"),
            ("mixed", "My_Profile-123"),
            ("uppercase", "PROFILE"),
            ("single_char", "p"),
        ]
    )
    def test_valid_profile_names(self, name, profile_name):
        """Test valid profile names are accepted."""
        config = S3ConnectionConfig(
            bucket_name=TEST_BUCKET,
            profile_name=profile_name,
        )
        self.assertEqual(config.profile_name, profile_name)

    @parameterized.expand(
        [
            ("with_space", "my profile"),
            ("with_at", "profile@test"),
            ("with_dot", "my.profile"),
            ("with_slash", "my/profile"),
            ("special_chars", "profile!@#"),
        ]
    )
    def test_invalid_profile_names_raise_error(self, name, profile_name):
        """Test invalid profile names raise error."""
        with self.assertRaises(ValidationError) as context:
            S3ConnectionConfig(
                bucket_name=TEST_BUCKET,
                profile_name=profile_name,
            )

        self.assertIn(
            "Profile name must only contain alphanumeric characters, hyphens (-), and underscores (_)",
            str(context.exception),
        )


class TestS3ConnectionConfigRegionNameValidation(unittest.TestCase):
    """Test suite for region name validation."""

    @parameterized.expand(
        [
            ("US East (Ohio)", "us-east-2"),
            ("US East (N. Virginia)", "us-east-1"),
            ("US West (N. California)", "us-west-1"),
            ("US West (Oregon)", "us-west-2"),
            ("Africa (Cape Town)", "af-south-1"),
            ("Asia Pacific (Hong Kong)", "ap-east-1"),
            ("Asia Pacific (Hyderabad)", "ap-south-2"),
            ("Asia Pacific (Jakarta)", "ap-southeast-3"),
            ("Asia Pacific (Malaysia)", "ap-southeast-5"),
            ("Asia Pacific (Melbourne)", "ap-southeast-4"),
            ("Asia Pacific (Mumbai)", "ap-south-1"),
            ("Asia Pacific (New Zealand)", "ap-southeast-6"),
            ("Asia Pacific (Osaka)", "ap-northeast-3"),
            ("Asia Pacific (Seoul)", "ap-northeast-2"),
            ("Asia Pacific (Singapore)", "ap-southeast-1"),
            ("Asia Pacific (Sydney)", "ap-southeast-2"),
            ("Asia Pacific (Taipei)", "ap-east-2"),
            ("Asia Pacific (Thailand)", "ap-southeast-7"),
            ("Asia Pacific (Tokyo)", "ap-northeast-1"),
            ("Canada (Central)", "ca-central-1"),
            ("Canada West (Calgary)", "ca-west-1"),
            ("Europe (Frankfurt)", "eu-central-1"),
            ("Europe (Ireland)", "eu-west-1"),
            ("Europe (London)", "eu-west-2"),
            ("Europe (Milan)", "eu-south-1"),
            ("Europe (Paris)", "eu-west-3"),
            ("Europe (Spain)", "eu-south-2"),
            ("Europe (Stockholm)", "eu-north-1"),
            ("Europe (Zurich)", "eu-central-2"),
            ("Israel (Tel Aviv)", "il-central-1"),
            ("Mexico (Central)", "mx-central-1"),
            ("Middle East (Bahrain)", "me-south-1"),
            ("Middle East (UAE)", "me-central-1"),
            ("South America (São Paulo)", "sa-east-1"),
            ("AWS GovCloud (US-East)", "us-gov-east-1"),
            ("AWS GovCloud (US-West)", "us-gov-west-1"),
        ]
    )
    def test_valid_public_region_names(self, name, region_name):
        """Test valid region names are accepted."""
        config = S3ConnectionConfig(
            bucket_name=TEST_BUCKET,
            profile_name=TEST_PROFILE,
            region_name=region_name,
        )
        self.assertEqual(config.region_name, region_name)

    @parameterized.expand(
        [
            (
                "Local Zone",
                "ap-southeast-1-lz-1",
            ),  # Valid region format (using local zone) but not a valid S3 region, though the parent region ap-southeast-1 is what's used for S3.
            ("European Sovereign Cloud", "eusc-de-east-1"),
            ("US Secret Region", "us-iso-east-1"),
            ("US Secret Region - Partition B", "us-isob-east-1"),
            ("Custom/Internal Location-based", "dc1-rack4-row2"),
            ("Custom/Internal Corporate-standard", "corp-internal-1"),
            ("Custom/Internal Cloud-mimic", "private-us-east-1"),
        ]
    )
    def test_valid_special_region_names(self, name, region_name):
        """Test valid region names are accepted."""
        config = S3ConnectionConfig(
            bucket_name=TEST_BUCKET,
            profile_name=TEST_PROFILE,
            region_name=region_name,
        )
        self.assertEqual(config.region_name, region_name)

    def test_none_region_is_valid(self):
        """Test that None region_name is valid."""
        config = S3ConnectionConfig(
            bucket_name=TEST_BUCKET,
            profile_name=TEST_PROFILE,
            region_name=None,
        )
        self.assertIsNone(config.region_name)

    @parameterized.expand(
        [
            ("uppercase", "US-EAST-1"),
            ("wrong_format", "useast1"),
            ("invalid_pattern", "us_east_1"),
            ("extra_hyphen", "us--east-1"),
            ("no_hyphen", "useast"),
            ("random_string", "random"),
        ]
    )
    def test_invalid_region_names_raise_error(self, name, region_name):
        """Test invalid region names raise error."""
        with self.assertRaises(ValidationError) as context:
            S3ConnectionConfig(
                bucket_name=TEST_BUCKET,
                profile_name=TEST_PROFILE,
                region_name=region_name,
            )

        self.assertIn(
            "Region name must be a valid lowercase AWS region",
            str(context.exception),
        )


class TestS3ConnectionConfigInheritance(unittest.TestCase):
    """Test suite for S3ConnectionConfig inheritance behavior."""

    def test_inherits_from_base_cloud_storage_config(self):
        """Test that S3ConnectionConfig inherits from BaseCloudStorageConnectionConfig."""
        from data_exchange_agent.config.sections.connections.cloud_storages.base import (
            BaseCloudStorageConnectionConfig,
        )

        config = S3ConnectionConfig(
            bucket_name=TEST_BUCKET,
            profile_name=TEST_PROFILE,
        )

        self.assertIsInstance(config, BaseCloudStorageConnectionConfig)

    def test_inherits_from_base_section_config(self):
        """Test that S3ConnectionConfig inherits from BaseSectionConfig."""
        from data_exchange_agent.config.sections.base_section_config import BaseSectionConfig

        config = S3ConnectionConfig(
            bucket_name=TEST_BUCKET,
            profile_name=TEST_PROFILE,
        )

        self.assertIsInstance(config, BaseSectionConfig)

    def test_getitem_works(self):
        """Test that __getitem__ from BaseSectionConfig works."""
        config = S3ConnectionConfig(
            bucket_name=TEST_BUCKET,
            profile_name=TEST_PROFILE,
            region_name=TEST_REGION,
        )

        self.assertEqual(config["bucket_name"], TEST_BUCKET)
        self.assertEqual(config["profile_name"], TEST_PROFILE)
        self.assertEqual(config["region_name"], TEST_REGION)

    def test_getitem_raises_keyerror_for_missing_key(self):
        """Test that __getitem__ raises KeyError for missing keys."""
        config = S3ConnectionConfig(
            bucket_name=TEST_BUCKET,
            profile_name=TEST_PROFILE,
        )

        with self.assertRaises(KeyError):
            _ = config["nonexistent"]


class TestS3ConnectionConfigEdgeCases(unittest.TestCase):
    """Test edge cases for S3ConnectionConfig."""

    def test_bucket_name_exactly_3_characters(self):
        """Test bucket name with exactly 3 characters (minimum)."""
        config = S3ConnectionConfig(
            bucket_name="abc",
            profile_name=TEST_PROFILE,
        )
        self.assertEqual(config.bucket_name, "abc")

    def test_bucket_name_exactly_63_characters(self):
        """Test bucket name with exactly 63 characters (maximum)."""
        bucket_name = "a" * 63
        config = S3ConnectionConfig(
            bucket_name=bucket_name,
            profile_name=TEST_PROFILE,
        )
        self.assertEqual(config.bucket_name, bucket_name)

    def test_bucket_name_with_all_numbers(self):
        """Test bucket name consisting only of numbers."""
        config = S3ConnectionConfig(
            bucket_name="1234567890",
            profile_name=TEST_PROFILE,
        )
        self.assertEqual(config.bucket_name, "1234567890")

    def test_multiple_dots_not_consecutive(self):
        """Test bucket name with multiple dots that are not consecutive."""
        config = S3ConnectionConfig(
            bucket_name="my.bucket.name.123",
            profile_name=TEST_PROFILE,
        )
        self.assertEqual(config.bucket_name, "my.bucket.name.123")

    def test_multiple_hyphens_not_consecutive(self):
        """Test bucket name with multiple hyphens."""
        config = S3ConnectionConfig(
            bucket_name="my-bucket-name-123",
            profile_name=TEST_PROFILE,
        )
        self.assertEqual(config.bucket_name, "my-bucket-name-123")


if __name__ == "__main__":
    unittest.main()
