from typing import Any

import pytest
from pydantic import BaseModel, ValidationError

from pydantic_extra_types.domain import DomainStr


class MyModel(BaseModel):
    domain: DomainStr


valid_domains = [
    'example.com',
    'sub.example.com',
    'sub-domain.example-site.co.uk',
    'a.com',
    'x.com',
    '1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.com',  # Multiple subdomains
]

invalid_domains = [
    '',  # Empty string
    'example',  # Missing TLD
    '.com',  # Missing domain name
    'example.',  # Trailing dot
    'exam ple.com',  # Space in domain
    'exa_mple.com',  # Underscore in domain
    'example.com.',  # Trailing dot
    '192.168.1.23',  # Ip address
    '192.168.1.0/23',  # CIDR
]

very_long_domains = [
    'a' * 249 + '.com',  # Just under the limit
    'a' * 250 + '.com',  # At the limit
    'a' * 251 + '.com',  # Just over the limit
    'sub1.sub2.sub3.sub4.sub5.sub6.sub7.sub8.sub9.sub10.sub11.sub12.sub13.sub14.sub15.sub16.sub17.sub18.sub19.sub20.sub21.sub22.sub23.sub24.sub25.sub26.sub27.sub28.sub29.sub30.sub31.sub32.sub33.extremely-long-domain-name-example-to-test-the-253-character-limit.com',
]

invalid_domain_types = [1, 2, 1.1, 2.1, False, [], {}, None]


@pytest.mark.parametrize('domain', valid_domains)
def test_valid_domains(domain: str):
    try:
        MyModel.model_validate({'domain': domain})
        assert len(domain) < 254 and len(domain) > 0
    except ValidationError:
        assert len(domain) > 254 or len(domain) == 0


@pytest.mark.parametrize('domain', invalid_domains)
def test_invalid_domains(domain: str):
    try:
        MyModel.model_validate({'domain': domain})
        raise Exception(
            f"This test case has only samples that should raise a ValidationError. This domain '{domain}' did not raise such an exception."
        )
    except ValidationError:
        # An error is expected on this test
        pass


@pytest.mark.parametrize('domain', very_long_domains)
def test_very_long_domains(domain: str):
    try:
        MyModel.model_validate({'domain': domain})
        assert len(domain) < 254 and len(domain) > 0
    except ValidationError:
        # An error is expected on this test
        pass


@pytest.mark.parametrize('domain', invalid_domain_types)
def test_invalid_domain_types(domain: Any):
    with pytest.raises(ValidationError, match='Value must be a string'):
        MyModel(domain=domain)


def test_domainstr_with_punycode():
    class Model(BaseModel):
        domain: DomainStr

    valid = 'xn--7-7sbirhro.xn--80ahmohdapg.xn--80asehdb'
    model = Model(domain=valid)
    assert model.domain == valid


def test_domainstr_invalid():
    class Model(BaseModel):
        domain: DomainStr

    try:
        Model(domain='invalid_domain')
        assert False, 'Expected ValidationError'
    except ValidationError:
        pass
