# tests/schemas/test_user_schemas.py
import re

import pytest
from pydantic import ValidationError

from app.schemas.users import UserBase, UserCreate, UserUpdate


class TestPhoneNumberValidation:
    """Tests for phone number validation in user schemas"""

    def test_valid_swiss_numbers(self):
        """Test valid Swiss phone numbers are accepted"""
        # International format
        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="+41791234567",
        )
        assert user.phone_number == "+41791234567"

        # Local format
        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="0791234567",
        )
        assert user.phone_number == "0791234567"

        # With formatting characters
        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="+41 79 123 45 67",
        )
        assert re.sub(r"[\s\-\(\)]", "", user.phone_number) == "+41791234567"

        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="079 123 45 67",
        )
        assert re.sub(r"[\s\-\(\)]", "", user.phone_number) == "0791234567"

        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="+41-79-123-45-67",
        )
        assert re.sub(r"[\s\-\(\)]", "", user.phone_number) == "+41791234567"

        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="079-123-45-67",
        )
        assert re.sub(r"[\s\-\(\)]", "", user.phone_number) == "0791234567"

        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="+41 (79) 123 45 67",
        )
        assert re.sub(r"[\s\-\(\)]", "", user.phone_number) == "+41791234567"

        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="079 (123) 45 67",
        )
        assert re.sub(r"[\s\-\(\)]", "", user.phone_number) == "0791234567"

    def test_valid_italian_numbers(self):
        """Test valid Italian phone numbers are accepted"""
        # International format
        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="+393451234567",
        )
        assert user.phone_number == "+393451234567"

        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="+39345123456",
        )
        assert user.phone_number == "+39345123456"

        # Local format
        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="03451234567",
        )
        assert user.phone_number == "03451234567"

        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="0345123456789",
        )
        assert user.phone_number == "0345123456789"

        # With formatting characters
        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="+39 345 123 4567",
        )
        assert re.sub(r"[\s\-\(\)]", "", user.phone_number) == "+393451234567"

        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="0345 123 4567",
        )
        assert re.sub(r"[\s\-\(\)]", "", user.phone_number) == "03451234567"

        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="+39-345-123-4567",
        )
        assert re.sub(r"[\s\-\(\)]", "", user.phone_number) == "+393451234567"

        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="0345-123-4567",
        )
        assert re.sub(r"[\s\-\(\)]", "", user.phone_number) == "03451234567"

        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="+39 (345) 123 4567",
        )
        assert re.sub(r"[\s\-\(\)]", "", user.phone_number) == "+393451234567"

        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number="0345 (123) 4567",
        )
        assert re.sub(r"[\s\-\(\)]", "", user.phone_number) == "03451234567"

    def test_none_phone_number(self):
        """Test that None is accepted as a valid value (optional phone number)"""
        user = UserBase(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            phone_number=None,
        )
        assert user.phone_number is None

    def test_invalid_phone_numbers(self):
        """Test that invalid phone numbers are rejected"""
        invalid_numbers = [
            # Too short
            "+12",
            "012",
            # Invalid characters
            "+41xyz123456",
            "079abc4567",
            "123-abc-7890",
            "+1(800)CALL-NOW",
            # Completely invalid formats
            "++4412345678",  # Double plus
            # Note: "()+41123456" becomes "+41123456" after cleaning, which is valid
            # Empty string
            "",
            # Spaces only
            "   ",
        ]

        for number in invalid_numbers:
            with pytest.raises(ValidationError):
                UserBase(
                    email="test@example.com",
                    first_name="Test",
                    last_name="User",
                    phone_number=number,
                )

    def test_phone_validation_in_user_create(self):
        """Test that phone validation also works in UserCreate schema"""
        # Valid phone number
        user = UserCreate(
            email="test@example.com",
            first_name="Test",
            last_name="User",
            password="Password123!",
            phone_number="+41791234567",
        )
        assert user.phone_number == "+41791234567"

        # Invalid phone number should raise ValidationError
        with pytest.raises(ValidationError):
            UserCreate(
                email="test@example.com",
                first_name="Test",
                last_name="User",
                password="Password123!",
                phone_number="invalid-number",
            )


class TestLocaleValidation:
    """Tests for locale validation in user schemas"""

    def test_valid_locale_en(self):
        """Test that 'en' locale is accepted"""
        user = UserUpdate(locale="en")
        assert user.locale == "en"

    def test_valid_locale_it(self):
        """Test that 'it' locale is accepted"""
        user = UserUpdate(locale="it")
        assert user.locale == "it"

    def test_valid_locale_en_us(self):
        """Test that 'en-US' locale is accepted and normalized to lowercase"""
        user = UserUpdate(locale="en-US")
        assert user.locale == "en-us"  # Normalized to lowercase

    def test_valid_locale_en_gb(self):
        """Test that 'en-GB' locale is accepted and normalized to lowercase"""
        user = UserUpdate(locale="en-GB")
        assert user.locale == "en-gb"  # Normalized to lowercase

    def test_valid_locale_it_it(self):
        """Test that 'it-IT' locale is accepted and normalized to lowercase"""
        user = UserUpdate(locale="it-IT")
        assert user.locale == "it-it"  # Normalized to lowercase

    def test_none_locale(self):
        """Test that None is accepted as a valid value (optional locale)"""
        user = UserUpdate(locale=None)
        assert user.locale is None

    def test_locale_not_provided(self):
        """Test that locale can be omitted entirely"""
        user = UserUpdate(first_name="Test")
        assert user.locale is None

    def test_unsupported_locale_french(self):
        """Test that unsupported locale 'fr' is rejected"""
        with pytest.raises(ValidationError) as exc_info:
            UserUpdate(locale="fr")

        # Verify error message mentions unsupported locale
        assert "Unsupported locale" in str(exc_info.value)

    def test_unsupported_locale_german(self):
        """Test that unsupported locale 'de' is rejected"""
        with pytest.raises(ValidationError) as exc_info:
            UserUpdate(locale="de")

        assert "Unsupported locale" in str(exc_info.value)

    def test_unsupported_locale_spanish(self):
        """Test that unsupported locale 'es' is rejected"""
        with pytest.raises(ValidationError) as exc_info:
            UserUpdate(locale="es")

        assert "Unsupported locale" in str(exc_info.value)

    def test_unsupported_locale_region_specific(self):
        """Test that unsupported region-specific locale is rejected"""
        with pytest.raises(ValidationError) as exc_info:
            UserUpdate(locale="it-CH")  # Italian (Switzerland) - not in supported list

        assert "Unsupported locale" in str(exc_info.value)

    def test_invalid_locale_format_no_dash(self):
        """Test that invalid format (no dash between components) is rejected"""
        with pytest.raises(ValidationError):
            UserUpdate(locale="enus")  # Should be "en-US"

    def test_invalid_locale_format_invalid_pattern(self):
        """Test that completely invalid format is rejected"""
        invalid_locales = [
            "en-us",  # Region code must be uppercase
            "EN",  # Language code must be lowercase
            "en-",  # Incomplete
            "-US",  # Incomplete
            "e",  # Too short
            "eng",  # Language code too long
            "en-USA",  # Region code too long
            "123",  # Numbers not allowed
            "en_US",  # Underscore not allowed (must be dash)
        ]

        for invalid_locale in invalid_locales:
            with pytest.raises(ValidationError):
                UserUpdate(locale=invalid_locale)

    def test_empty_string_locale(self):
        """Test that empty string is rejected"""
        with pytest.raises(ValidationError):
            UserUpdate(locale="")

    def test_locale_with_whitespace(self):
        """Test that locale with whitespace is rejected"""
        with pytest.raises(ValidationError):
            UserUpdate(locale=" en ")

    def test_locale_max_length(self):
        """Test that locale exceeding max length (10 chars) is rejected"""
        with pytest.raises(ValidationError):
            UserUpdate(locale="en-US-extra")  # 12 characters, exceeds max_length=10

    def test_locale_in_user_update_with_other_fields(self):
        """Test locale validation works when combined with other fields"""
        # Valid locale with other fields
        user = UserUpdate(first_name="Mario", last_name="Rossi", locale="it")
        assert user.locale == "it"
        assert user.first_name == "Mario"

        # Invalid locale with other valid fields should still fail
        with pytest.raises(ValidationError):
            UserUpdate(
                first_name="Pierre",
                last_name="Dupont",
                locale="fr",  # Unsupported
            )

    def test_supported_locales_list(self):
        """Test all supported locales are accepted and normalized"""
        # Input locales (mixed case)
        input_locales = ["en", "it", "en-US", "en-GB", "it-IT"]
        # Expected output (normalized to lowercase)
        expected_outputs = ["en", "it", "en-us", "en-gb", "it-it"]

        for input_locale, expected_output in zip(
            input_locales, expected_outputs, strict=True
        ):
            user = UserUpdate(locale=input_locale)
            assert user.locale == expected_output

    def test_locale_error_message_shows_supported_locales(self):
        """Test that error message lists supported locales"""
        with pytest.raises(ValidationError) as exc_info:
            UserUpdate(locale="fr")

        error_str = str(exc_info.value)
        # Should mention supported locales in error
        assert "en" in error_str or "it" in error_str
        assert "Supported locales" in error_str
