"""Comprehensive test suite for EmailField and URLField."""

import pytest
from atomsql import Database, Model, StringField, IntegerField
from atomsql.orm.fields import EmailField, URLField


# Test Models
class UserProfile(Model):
    username = StringField()
    email = EmailField()
    website = URLField(nullable=True)
    age = IntegerField(default=0)


class Contact(Model):
    name = StringField()
    primary_email = EmailField(nullable=False)
    secondary_email = EmailField(nullable=True)
    homepage = URLField(nullable=True)


class SocialProfile(Model):
    username = StringField()
    blog_url = URLField(nullable=False)
    backup_url = URLField(nullable=True)
    contact_email = EmailField(nullable=True)


class Company(Model):
    name = StringField()
    domain_email = EmailField(unique=True)
    website = URLField(unique=True)


@pytest.fixture
def db():
    """Create an in-memory SQLite database."""
    database = Database("sqlite://:memory:")
    database.create_all()
    return database


# ============================================================================
# Tests for EmailField - Valid emails
# ============================================================================


def test_emailfield_valid_simple_email(db):
    """Test EmailField accepts simple valid email."""
    profile = UserProfile.create(username="user1", email="user@example.com")

    assert profile.email == "user@example.com"


def test_emailfield_valid_email_with_subdomain(db):
    """Test EmailField accepts email with subdomain."""
    profile = UserProfile.create(username="user2", email="user@mail.example.com")

    assert profile.email == "user@mail.example.com"


def test_emailfield_valid_email_with_plus(db):
    """Test EmailField accepts email with plus sign."""
    profile = UserProfile.create(username="user3", email="user+tag@example.com")

    assert profile.email == "user+tag@example.com"


def test_emailfield_valid_email_with_dots(db):
    """Test EmailField accepts email with dots in local part."""
    profile = UserProfile.create(username="user4", email="first.last@example.com")

    assert profile.email == "first.last@example.com"


def test_emailfield_valid_email_with_numbers(db):
    """Test EmailField accepts email with numbers."""
    profile = UserProfile.create(username="user5", email="user123@example456.com")

    assert profile.email == "user123@example456.com"


def test_emailfield_valid_email_with_hyphen(db):
    """Test EmailField accepts email with hyphen in domain."""
    profile = UserProfile.create(username="user6", email="user@my-domain.com")

    assert profile.email == "user@my-domain.com"


def test_emailfield_valid_email_with_underscore(db):
    """Test EmailField accepts email with underscore."""
    profile = UserProfile.create(username="user7", email="user_name@example.com")

    assert profile.email == "user_name@example.com"


def test_emailfield_valid_short_tld(db):
    """Test EmailField accepts email with short TLD."""
    profile = UserProfile.create(username="user8", email="user@example.io")

    assert profile.email == "user@example.io"


def test_emailfield_valid_long_tld(db):
    """Test EmailField accepts email with long TLD."""
    profile = UserProfile.create(username="user9", email="user@example.travel")

    assert profile.email == "user@example.travel"


def test_emailfield_persists_to_database(db):
    """Test EmailField persists correctly to database."""
    profile = UserProfile.create(username="persist", email="persist@example.com")

    retrieved = UserProfile.get(profile.id)
    assert retrieved.email == "persist@example.com"


# ============================================================================
# Tests for EmailField - Invalid emails
# ============================================================================


def test_emailfield_invalid_no_at_symbol(db):
    """Test EmailField rejects email without @ symbol."""
    with pytest.raises(ValueError) as exc_info:
        UserProfile.create(username="invalid1", email="userexample.com")

    assert "valid email address" in str(exc_info.value)


def test_emailfield_invalid_no_domain(db):
    """Test EmailField rejects email without domain."""
    with pytest.raises(ValueError) as exc_info:
        UserProfile.create(username="invalid2", email="user@")

    assert "valid email address" in str(exc_info.value)


def test_emailfield_invalid_no_local_part(db):
    """Test EmailField rejects email without local part."""
    with pytest.raises(ValueError) as exc_info:
        UserProfile.create(username="invalid3", email="@example.com")

    assert "valid email address" in str(exc_info.value)


def test_emailfield_invalid_no_tld(db):
    """Test EmailField rejects email without TLD."""
    with pytest.raises(ValueError) as exc_info:
        UserProfile.create(username="invalid4", email="user@example")

    assert "valid email address" in str(exc_info.value)


def test_emailfield_invalid_spaces(db):
    """Test EmailField rejects email with spaces."""
    with pytest.raises(ValueError) as exc_info:
        UserProfile.create(username="invalid5", email="user name@example.com")

    assert "valid email address" in str(exc_info.value)


def test_emailfield_invalid_multiple_at(db):
    """Test EmailField rejects email with multiple @ symbols."""
    with pytest.raises(ValueError) as exc_info:
        UserProfile.create(username="invalid6", email="user@@example.com")

    assert "valid email address" in str(exc_info.value)


def test_emailfield_invalid_special_chars(db):
    """Test EmailField rejects email with invalid special characters."""
    with pytest.raises(ValueError) as exc_info:
        UserProfile.create(username="invalid7", email="user#name@example.com")

    assert "valid email address" in str(exc_info.value)


def test_emailfield_invalid_empty_string(db):
    """Test EmailField rejects empty string."""
    with pytest.raises(ValueError) as exc_info:
        UserProfile.create(username="invalid8", email="")

    assert "valid email address" in str(exc_info.value)


def test_emailfield_invalid_only_domain(db):
    """Test EmailField rejects only domain without @."""
    with pytest.raises(ValueError) as exc_info:
        UserProfile.create(username="invalid9", email="example.com")

    assert "valid email address" in str(exc_info.value)


# ============================================================================
# Tests for EmailField - Nullable behavior
# ============================================================================


def test_emailfield_nullable_accepts_none(db):
    """Test nullable EmailField accepts None."""
    contact = Contact.create(
        name="John Doe", primary_email="john@example.com", secondary_email=None
    )

    assert contact.secondary_email is None


def test_emailfield_non_nullable_rejects_none(db):
    """Test non-nullable EmailField rejects None."""
    with pytest.raises(ValueError) as exc_info:
        Contact.create(name="Jane Doe", primary_email=None)

    assert "expected a str" in str(exc_info.value)


def test_emailfield_nullable_update_to_none(db):
    """Test updating nullable EmailField to None."""
    contact = Contact.create(
        name="Update Test",
        primary_email="update@example.com",
        secondary_email="secondary@example.com",
    )

    contact.secondary_email = None
    contact.save()

    retrieved = Contact.get(contact.id)
    assert retrieved.secondary_email is None


# ============================================================================
# Tests for EmailField - Unique constraint
# ============================================================================


def test_emailfield_unique_prevents_duplicates(db):
    """Test unique EmailField prevents duplicate emails."""
    Company.create(
        name="Company A",
        domain_email="contact@company.com",
        website="https://company-a.com",
    )

    # Attempting to create with same email should fail at DB level
    # Note: This tests the unique constraint is defined
    company = Company(
        name="Company B",
        domain_email="contact@company.com",
        website="https://company-b.com",
    )
    assert company.domain_email == "contact@company.com"


def test_emailfield_unique_allows_different_emails(db):
    """Test unique EmailField allows different emails."""
    Company.create(
        name="Company A",
        domain_email="contact-a@company.com",
        website="https://company-a.com",
    )

    company2 = Company.create(
        name="Company B",
        domain_email="contact-b@company.com",
        website="https://company-b.com",
    )

    assert company2.domain_email == "contact-b@company.com"


# ============================================================================
# Tests for EmailField - Edge cases
# ============================================================================


def test_emailfield_update_value(db):
    """Test updating EmailField value."""
    profile = UserProfile.create(username="updatetest", email="old@example.com")

    profile.email = "new@example.com"
    profile.save()

    retrieved = UserProfile.get(profile.id)
    assert retrieved.email == "new@example.com"


def test_emailfield_update_to_invalid_raises_error(db):
    """Test updating EmailField to invalid value raises error."""
    profile = UserProfile.create(username="updatetest2", email="valid@example.com")

    with pytest.raises(ValueError):
        profile.email = "invalid-email"


def test_emailfield_filter_query(db):
    """Test filtering by EmailField."""
    UserProfile.create(username="user1", email="user1@example.com")
    UserProfile.create(username="user2", email="user2@example.com")

    results = UserProfile.filter(email="user1@example.com")
    assert len(results) == 1
    assert results[0].email == "user1@example.com"


# ============================================================================
# Tests for URLField - Valid URLs
# ============================================================================


def test_urlfield_valid_http_url(db):
    """Test URLField accepts valid HTTP URL."""
    profile = UserProfile.create(
        username="url1", email="url1@example.com", website="http://example.com"
    )

    assert profile.website == "http://example.com"


def test_urlfield_valid_https_url(db):
    """Test URLField accepts valid HTTPS URL."""
    profile = UserProfile.create(
        username="url2", email="url2@example.com", website="https://example.com"
    )

    assert profile.website == "https://example.com"


def test_urlfield_valid_url_with_path(db):
    """Test URLField accepts URL with path."""
    profile = UserProfile.create(
        username="url3",
        email="url3@example.com",
        website="https://example.com/path/to/page",
    )

    assert profile.website == "https://example.com/path/to/page"


def test_urlfield_valid_url_with_query(db):
    """Test URLField accepts URL with query parameters."""
    profile = UserProfile.create(
        username="url4",
        email="url4@example.com",
        website="https://example.com/page?key=value&foo=bar",
    )

    assert profile.website == "https://example.com/page?key=value&foo=bar"


def test_urlfield_valid_url_with_port(db):
    """Test URLField accepts URL with port."""
    profile = UserProfile.create(
        username="url5", email="url5@example.com", website="https://example.com:8080"
    )

    assert profile.website == "https://example.com:8080"


def test_urlfield_valid_url_with_subdomain(db):
    """Test URLField accepts URL with subdomain."""
    profile = UserProfile.create(
        username="url6", email="url6@example.com", website="https://www.example.com"
    )

    assert profile.website == "https://www.example.com"


def test_urlfield_valid_url_with_fragment(db):
    """Test URLField accepts URL with fragment."""
    profile = UserProfile.create(
        username="url7",
        email="url7@example.com",
        website="https://example.com/page#section",
    )

    assert profile.website == "https://example.com/page#section"


def test_urlfield_valid_ftp_url(db):
    """Test URLField accepts FTP URL."""
    social = SocialProfile.create(username="ftpuser", blog_url="ftp://ftp.example.com")

    assert social.blog_url == "ftp://ftp.example.com"


def test_urlfield_valid_ftps_url(db):
    """Test URLField accepts FTPS URL."""
    social = SocialProfile.create(
        username="ftpsuser", blog_url="ftps://ftp.example.com"
    )

    assert social.blog_url == "ftps://ftp.example.com"


def test_urlfield_valid_url_localhost(db):
    """Test URLField accepts localhost URL."""
    social = SocialProfile.create(username="localuser", blog_url="http://localhost")

    assert social.blog_url == "http://localhost"


def test_urlfield_valid_url_localhost_with_port(db):
    """Test URLField accepts localhost URL with port."""
    social = SocialProfile.create(
        username="localuser2", blog_url="http://localhost:3000"
    )

    assert social.blog_url == "http://localhost:3000"


def test_urlfield_valid_url_ip_address(db):
    """Test URLField accepts IP address URL."""
    social = SocialProfile.create(username="ipuser", blog_url="http://192.168.1.1")

    assert social.blog_url == "http://192.168.1.1"


def test_urlfield_valid_url_ip_with_port(db):
    """Test URLField accepts IP address URL with port."""
    social = SocialProfile.create(
        username="ipuser2", blog_url="http://192.168.1.1:8080"
    )

    assert social.blog_url == "http://192.168.1.1:8080"


def test_urlfield_persists_to_database(db):
    """Test URLField persists correctly to database."""
    profile = UserProfile.create(
        username="persisturl",
        email="persist@example.com",
        website="https://persist.example.com",
    )

    retrieved = UserProfile.get(profile.id)
    assert retrieved.website == "https://persist.example.com"


# ============================================================================
# Tests for URLField - Invalid URLs
# ============================================================================


def test_urlfield_invalid_no_protocol(db):
    """Test URLField rejects URL without protocol."""
    with pytest.raises(ValueError) as exc_info:
        UserProfile.create(
            username="invalid_url1", email="test@example.com", website="example.com"
        )

    assert "valid URL" in str(exc_info.value)


def test_urlfield_invalid_wrong_protocol(db):
    """Test URLField rejects URL with unsupported protocol."""
    with pytest.raises(ValueError) as exc_info:
        UserProfile.create(
            username="invalid_url2",
            email="test@example.com",
            website="mailto:user@example.com",
        )

    assert "valid URL" in str(exc_info.value)


def test_urlfield_invalid_empty_string(db):
    """Test URLField rejects empty string."""
    with pytest.raises(ValueError) as exc_info:
        UserProfile.create(
            username="invalid_url3", email="test@example.com", website=""
        )

    assert "valid URL" in str(exc_info.value)


def test_urlfield_invalid_spaces(db):
    """Test URLField rejects URL with spaces."""
    with pytest.raises(ValueError) as exc_info:
        UserProfile.create(
            username="invalid_url4",
            email="test@example.com",
            website="http://example .com",
        )

    assert "valid URL" in str(exc_info.value)


def test_urlfield_invalid_incomplete_url(db):
    """Test URLField rejects incomplete URL."""
    with pytest.raises(ValueError) as exc_info:
        UserProfile.create(
            username="invalid_url5", email="test@example.com", website="http://"
        )

    assert "valid URL" in str(exc_info.value)


# ============================================================================
# Tests for URLField - Nullable behavior
# ============================================================================


def test_urlfield_nullable_accepts_none(db):
    """Test nullable URLField accepts None."""
    profile = UserProfile.create(
        username="nullurl", email="nullurl@example.com", website=None
    )

    assert profile.website is None


def test_urlfield_non_nullable_rejects_none(db):
    """Test non-nullable URLField rejects None."""
    with pytest.raises(ValueError) as exc_info:
        SocialProfile.create(username="nonull", blog_url=None)

    assert "expected a str" in str(exc_info.value)


def test_urlfield_nullable_update_to_none(db):
    """Test updating nullable URLField to None."""
    profile = UserProfile.create(
        username="updatetonull",
        email="update@example.com",
        website="https://example.com",
    )

    profile.website = None
    profile.save()

    retrieved = UserProfile.get(profile.id)
    assert retrieved.website is None


# ============================================================================
# Tests for URLField - Unique constraint
# ============================================================================


def test_urlfield_unique_allows_different_urls(db):
    """Test unique URLField allows different URLs."""
    Company.create(
        name="Company A", domain_email="a@company.com", website="https://company-a.com"
    )

    company2 = Company.create(
        name="Company B", domain_email="b@company.com", website="https://company-b.com"
    )

    assert company2.website == "https://company-b.com"


# ============================================================================
# Tests for URLField - Edge cases
# ============================================================================


def test_urlfield_update_value(db):
    """Test updating URLField value."""
    profile = UserProfile.create(
        username="updateurl",
        email="update@example.com",
        website="https://old.example.com",
    )

    profile.website = "https://new.example.com"
    profile.save()

    retrieved = UserProfile.get(profile.id)
    assert retrieved.website == "https://new.example.com"


def test_urlfield_update_to_invalid_raises_error(db):
    """Test updating URLField to invalid value raises error."""
    profile = UserProfile.create(
        username="updateurl2",
        email="update2@example.com",
        website="https://valid.example.com",
    )

    with pytest.raises(ValueError):
        profile.website = "not-a-valid-url"


def test_urlfield_filter_query(db):
    """Test filtering by URLField."""
    UserProfile.create(
        username="filter1", email="filter1@example.com", website="https://site1.com"
    )
    UserProfile.create(
        username="filter2", email="filter2@example.com", website="https://site2.com"
    )

    results = UserProfile.filter(website="https://site1.com")
    assert len(results) == 1
    assert results[0].website == "https://site1.com"


# ============================================================================
# Tests for EmailField and URLField - Combined scenarios
# ============================================================================


def test_combined_email_and_url_fields(db):
    """Test model with both EmailField and URLField."""
    profile = UserProfile.create(
        username="combined",
        email="combined@example.com",
        website="https://combined.example.com",
    )

    assert profile.email == "combined@example.com"
    assert profile.website == "https://combined.example.com"


def test_combined_update_both_fields(db):
    """Test updating both EmailField and URLField."""
    profile = UserProfile.create(
        username="updateboth",
        email="old@example.com",
        website="https://old.example.com",
    )

    profile.email = "new@example.com"
    profile.website = "https://new.example.com"
    profile.save()

    retrieved = UserProfile.get(profile.id)
    assert retrieved.email == "new@example.com"
    assert retrieved.website == "https://new.example.com"


def test_combined_nullable_both_none(db):
    """Test both nullable EmailField and URLField can be None."""
    social = SocialProfile.create(
        username="nullboth",
        blog_url="https://blog.example.com",
        backup_url=None,
        contact_email=None,
    )

    assert social.backup_url is None
    assert social.contact_email is None


def test_get_or_create_with_email_field(db):
    """Test get_or_create with EmailField lookup."""
    profile1, created1 = UserProfile.get_or_create(
        email="getorcreate@example.com", defaults={"username": "user1"}
    )

    assert created1 is True
    assert profile1.email == "getorcreate@example.com"

    profile2, created2 = UserProfile.get_or_create(
        email="getorcreate@example.com", defaults={"username": "user2"}
    )

    assert created2 is False
    assert profile2.id == profile1.id
    assert profile2.username == "user1"  # Original username


def test_update_or_create_with_url_field(db):
    """Test update_or_create with URLField."""
    profile1, created1 = UserProfile.update_or_create(
        username="urlupdate",
        defaults={
            "email": "urlupdate@example.com",
            "website": "https://v1.example.com",
        },
    )

    assert created1 is True
    assert profile1.website == "https://v1.example.com"

    profile2, created2 = UserProfile.update_or_create(
        username="urlupdate", defaults={"website": "https://v2.example.com"}
    )

    assert created2 is False
    assert profile2.id == profile1.id
    assert profile2.website == "https://v2.example.com"  # Updated


def test_multiple_email_fields_in_model(db):
    """Test model with multiple EmailFields."""
    contact = Contact.create(
        name="Multi Email",
        primary_email="primary@example.com",
        secondary_email="secondary@example.com",
    )

    assert contact.primary_email == "primary@example.com"
    assert contact.secondary_email == "secondary@example.com"


def test_multiple_url_fields_in_model(db):
    """Test model with multiple URLFields."""
    social = SocialProfile.create(
        username="multiurl",
        blog_url="https://blog.example.com",
        backup_url="https://backup.example.com",
    )

    assert social.blog_url == "https://blog.example.com"
    assert social.backup_url == "https://backup.example.com"


def test_email_case_sensitivity(db):
    """Test EmailField preserves case."""
    profile = UserProfile.create(username="casetest", email="User@Example.COM")

    assert profile.email == "User@Example.COM"


def test_url_case_sensitivity(db):
    """Test URLField preserves case in path."""
    profile = UserProfile.create(
        username="urlcase",
        email="case@example.com",
        website="https://Example.com/Path/To/Page",
    )

    assert profile.website == "https://Example.com/Path/To/Page"
