# tests/unit/services/test_store_domain_service.py
"""Unit tests for StoreDomainService."""

import uuid
from datetime import UTC, datetime
from unittest.mock import MagicMock, patch

import pytest
from pydantic import ValidationError

from app.modules.tenancy.exceptions import (
    DomainAlreadyVerifiedException,
    DomainNotVerifiedException,
    DomainVerificationFailedException,
    MaxDomainsReachedException,
    StoreDomainAlreadyExistsException,
    StoreDomainNotFoundException,
    StoreNotFoundException,
)
from app.modules.tenancy.models import StoreDomain
from app.modules.tenancy.schemas.store_domain import (
    StoreDomainCreate,
    StoreDomainUpdate,
)
from app.modules.tenancy.services.store_domain_service import store_domain_service

# =============================================================================
# FIXTURES
# =============================================================================


@pytest.fixture
def test_domain(db, test_store):
    """Create a test domain for a store."""
    unique_id = str(uuid.uuid4())[:8]
    domain = StoreDomain(
        store_id=test_store.id,
        domain=f"test{unique_id}.example.com",
        is_primary=False,
        is_active=False,
        is_verified=False,
        verification_token=f"token_{unique_id}",
        ssl_status="pending",
    )
    db.add(domain)
    db.commit()
    db.refresh(domain)
    return domain


@pytest.fixture
def verified_domain(db, test_store):
    """Create a verified domain for a store."""
    unique_id = str(uuid.uuid4())[:8]
    domain = StoreDomain(
        store_id=test_store.id,
        domain=f"verified{unique_id}.example.com",
        is_primary=False,
        is_active=True,
        is_verified=True,
        verified_at=datetime.now(UTC),
        verification_token=f"verified_token_{unique_id}",
        ssl_status="active",
    )
    db.add(domain)
    db.commit()
    db.refresh(domain)
    return domain


@pytest.fixture
def primary_domain(db, test_store):
    """Create a primary domain for a store."""
    unique_id = str(uuid.uuid4())[:8]
    domain = StoreDomain(
        store_id=test_store.id,
        domain=f"primary{unique_id}.example.com",
        is_primary=True,
        is_active=True,
        is_verified=True,
        verified_at=datetime.now(UTC),
        verification_token=f"primary_token_{unique_id}",
        ssl_status="active",
    )
    db.add(domain)
    db.commit()
    db.refresh(domain)
    return domain


# =============================================================================
# ADD DOMAIN TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.tenancy
class TestStoreDomainServiceAdd:
    """Test suite for adding store domains."""

    def test_add_domain_success(self, db, test_store):
        """Test successfully adding a domain to a store."""
        unique_id = str(uuid.uuid4())[:8]
        domain_data = StoreDomainCreate(
            domain=f"newdomain{unique_id}.example.com",
            is_primary=False,
        )

        result = store_domain_service.add_domain(db, test_store.id, domain_data)
        db.commit()

        assert result is not None
        assert result.store_id == test_store.id
        assert result.domain == f"newdomain{unique_id}.example.com"
        assert result.is_primary is False
        assert result.is_verified is False
        assert result.is_active is False
        assert result.verification_token is not None

    def test_add_domain_as_primary(self, db, test_store, primary_domain):
        """Test adding a domain as primary unsets other primary domains."""
        unique_id = str(uuid.uuid4())[:8]
        domain_data = StoreDomainCreate(
            domain=f"newprimary{unique_id}.example.com",
            is_primary=True,
        )

        result = store_domain_service.add_domain(db, test_store.id, domain_data)
        db.commit()
        db.refresh(primary_domain)

        assert result.is_primary is True
        assert primary_domain.is_primary is False

    def test_add_domain_store_not_found(self, db):
        """Test adding domain to non-existent store raises exception."""
        domain_data = StoreDomainCreate(
            domain="test.example.com",
            is_primary=False,
        )

        with pytest.raises(StoreNotFoundException):
            store_domain_service.add_domain(db, 99999, domain_data)

    def test_add_domain_already_exists(self, db, test_store, test_domain):
        """Test adding a domain that already exists raises exception."""
        domain_data = StoreDomainCreate(
            domain=test_domain.domain,
            is_primary=False,
        )

        with pytest.raises(StoreDomainAlreadyExistsException):
            store_domain_service.add_domain(db, test_store.id, domain_data)

    def test_add_domain_max_limit_reached(self, db, test_store):
        """Test adding domain when max limit reached raises exception."""
        # Create max domains
        for i in range(store_domain_service.max_domains_per_store):
            domain = StoreDomain(
                store_id=test_store.id,
                domain=f"domain{i}_{uuid.uuid4().hex[:6]}.example.com",
                verification_token=f"token_{i}_{uuid.uuid4().hex[:6]}",
            )
            db.add(domain)  # noqa: PERF006
        db.commit()

        domain_data = StoreDomainCreate(
            domain="onemore.example.com",
            is_primary=False,
        )

        with pytest.raises(MaxDomainsReachedException):
            store_domain_service.add_domain(db, test_store.id, domain_data)

    def test_add_domain_reserved_subdomain(self, db, test_store):
        """Test adding a domain with reserved subdomain raises exception.

        Note: Reserved subdomain validation happens in Pydantic schema first.
        """
        with pytest.raises(ValidationError) as exc_info:
            StoreDomainCreate(
                domain="admin.example.com",
                is_primary=False,
            )

        assert "reserved subdomain" in str(exc_info.value).lower()


# =============================================================================
# GET DOMAINS TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.tenancy
class TestStoreDomainServiceGet:
    """Test suite for getting store domains."""

    def test_get_store_domains_success(self, db, test_store, test_domain, verified_domain):
        """Test getting all domains for a store."""
        domains = store_domain_service.get_store_domains(db, test_store.id)

        assert len(domains) >= 2
        domain_ids = [d.id for d in domains]
        assert test_domain.id in domain_ids
        assert verified_domain.id in domain_ids

    def test_get_store_domains_empty(self, db, test_store):
        """Test getting domains for store with no domains."""
        domains = store_domain_service.get_store_domains(db, test_store.id)

        # May have domains from other fixtures, so just check it returns a list
        assert isinstance(domains, list)

    def test_get_store_domains_store_not_found(self, db):
        """Test getting domains for non-existent store raises exception."""
        with pytest.raises(StoreNotFoundException):
            store_domain_service.get_store_domains(db, 99999)

    def test_get_domain_by_id_success(self, db, test_domain):
        """Test getting a domain by ID."""
        domain = store_domain_service.get_domain_by_id(db, test_domain.id)

        assert domain is not None
        assert domain.id == test_domain.id
        assert domain.domain == test_domain.domain

    def test_get_domain_by_id_not_found(self, db):
        """Test getting non-existent domain raises exception."""
        with pytest.raises(StoreDomainNotFoundException):
            store_domain_service.get_domain_by_id(db, 99999)


# =============================================================================
# UPDATE DOMAIN TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.tenancy
class TestStoreDomainServiceUpdate:
    """Test suite for updating store domains."""

    def test_update_domain_set_primary(self, db, test_domain, primary_domain):
        """Test setting a domain as primary."""
        update_data = StoreDomainUpdate(is_primary=True)

        # First verify the domain (required for activation)
        test_domain.is_verified = True
        db.commit()

        result = store_domain_service.update_domain(db, test_domain.id, update_data)
        db.commit()
        db.refresh(primary_domain)

        assert result.is_primary is True
        assert primary_domain.is_primary is False

    def test_update_domain_activate_verified(self, db, verified_domain):
        """Test activating a verified domain."""
        verified_domain.is_active = False
        db.commit()

        update_data = StoreDomainUpdate(is_active=True)

        result = store_domain_service.update_domain(db, verified_domain.id, update_data)
        db.commit()

        assert result.is_active is True

    def test_update_domain_activate_unverified_fails(self, db, test_domain):
        """Test activating an unverified domain raises exception."""
        update_data = StoreDomainUpdate(is_active=True)

        with pytest.raises(DomainNotVerifiedException):
            store_domain_service.update_domain(db, test_domain.id, update_data)

    def test_update_domain_not_found(self, db):
        """Test updating non-existent domain raises exception."""
        update_data = StoreDomainUpdate(is_primary=True)

        with pytest.raises(StoreDomainNotFoundException):
            store_domain_service.update_domain(db, 99999, update_data)

    def test_update_domain_deactivate(self, db, verified_domain):
        """Test deactivating a domain."""
        update_data = StoreDomainUpdate(is_active=False)

        result = store_domain_service.update_domain(db, verified_domain.id, update_data)
        db.commit()

        assert result.is_active is False


# =============================================================================
# DELETE DOMAIN TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.tenancy
class TestStoreDomainServiceDelete:
    """Test suite for deleting store domains."""

    def test_delete_domain_success(self, db, test_store):
        """Test successfully deleting a domain."""
        # Create a domain to delete
        unique_id = str(uuid.uuid4())[:8]
        domain = StoreDomain(
            store_id=test_store.id,
            domain=f"todelete{unique_id}.example.com",
            verification_token=f"delete_token_{unique_id}",
        )
        db.add(domain)
        db.commit()
        domain_id = domain.id
        domain_name = domain.domain

        result = store_domain_service.delete_domain(db, domain_id)
        db.commit()

        assert domain_name in result

        # Verify it's gone
        with pytest.raises(StoreDomainNotFoundException):
            store_domain_service.get_domain_by_id(db, domain_id)

    def test_delete_domain_not_found(self, db):
        """Test deleting non-existent domain raises exception."""
        with pytest.raises(StoreDomainNotFoundException):
            store_domain_service.delete_domain(db, 99999)


# =============================================================================
# VERIFY DOMAIN TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.tenancy
class TestStoreDomainServiceVerify:
    """Test suite for domain verification."""

    @patch("dns.resolver.resolve")
    def test_verify_domain_success(self, mock_resolve, db, test_domain):
        """Test successful domain verification."""
        # Mock DNS response
        mock_txt = MagicMock()
        mock_txt.to_text.return_value = f'"{test_domain.verification_token}"'
        mock_resolve.return_value = [mock_txt]

        domain, message = store_domain_service.verify_domain(db, test_domain.id)
        db.commit()

        assert domain.is_verified is True
        assert domain.verified_at is not None
        assert "verified successfully" in message.lower()

    def test_verify_domain_already_verified(self, db, verified_domain):
        """Test verifying already verified domain raises exception."""
        with pytest.raises(DomainAlreadyVerifiedException):
            store_domain_service.verify_domain(db, verified_domain.id)

    def test_verify_domain_not_found(self, db):
        """Test verifying non-existent domain raises exception."""
        with pytest.raises(StoreDomainNotFoundException):
            store_domain_service.verify_domain(db, 99999)

    @patch("dns.resolver.resolve")
    def test_verify_domain_token_not_found(self, mock_resolve, db, test_domain):
        """Test verification fails when token not found in DNS."""
        # Mock DNS response with wrong token
        mock_txt = MagicMock()
        mock_txt.to_text.return_value = '"wrong_token"'
        mock_resolve.return_value = [mock_txt]

        with pytest.raises(DomainVerificationFailedException) as exc_info:
            store_domain_service.verify_domain(db, test_domain.id)

        assert "token not found" in str(exc_info.value).lower()

    @patch("dns.resolver.resolve")
    def test_verify_domain_dns_nxdomain(self, mock_resolve, db, test_domain):
        """Test verification fails when DNS record doesn't exist."""
        import dns.resolver

        mock_resolve.side_effect = dns.resolver.NXDOMAIN()

        with pytest.raises(DomainVerificationFailedException):
            store_domain_service.verify_domain(db, test_domain.id)


# =============================================================================
# VERIFICATION INSTRUCTIONS TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.tenancy
class TestStoreDomainServiceInstructions:
    """Test suite for verification instructions."""

    def test_get_verification_instructions(self, db, test_domain):
        """Test getting verification instructions."""
        instructions = store_domain_service.get_verification_instructions(
            db, test_domain.id
        )

        assert instructions["domain"] == test_domain.domain
        assert instructions["verification_token"] == test_domain.verification_token
        assert "instructions" in instructions
        assert "txt_record" in instructions
        assert instructions["txt_record"]["type"] == "TXT"
        assert instructions["txt_record"]["name"] == "_orion-verify"
        assert "common_registrars" in instructions

    def test_get_verification_instructions_not_found(self, db):
        """Test getting instructions for non-existent domain raises exception."""
        with pytest.raises(StoreDomainNotFoundException):
            store_domain_service.get_verification_instructions(db, 99999)
