# tests/unit/services/test_vendor_email_settings_service.py
"""Unit tests for VendorEmailSettingsService."""

import pytest
from datetime import datetime, timezone
from unittest.mock import patch, MagicMock

from app.exceptions import (
    AuthorizationException,
    ResourceNotFoundException,
    ValidationException,
)
from app.modules.cms.services.vendor_email_settings_service import vendor_email_settings_service
from app.modules.messaging.models import VendorEmailSettings
from app.modules.billing.models import TierCode


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


@pytest.fixture
def test_email_settings(db, test_vendor):
    """Create test email settings for a vendor."""
    settings = VendorEmailSettings(
        vendor_id=test_vendor.id,
        from_email="test@example.com",
        from_name="Test Sender",
        provider="smtp",
        smtp_host="smtp.example.com",
        smtp_port=587,
        smtp_username="testuser",
        smtp_password="testpass",
        smtp_use_tls=True,
        smtp_use_ssl=False,
        is_configured=True,
        is_verified=False,
    )
    db.add(settings)
    db.commit()
    db.refresh(settings)
    return settings


@pytest.fixture
def test_verified_email_settings(db, test_vendor):
    """Create verified email settings."""
    settings = VendorEmailSettings(
        vendor_id=test_vendor.id,
        from_email="verified@example.com",
        from_name="Verified Sender",
        provider="smtp",
        smtp_host="smtp.example.com",
        smtp_port=587,
        smtp_username="testuser",
        smtp_password="testpass",
        smtp_use_tls=True,
        is_configured=True,
        is_verified=True,
        last_verified_at=datetime.now(timezone.utc),
    )
    db.add(settings)
    db.commit()
    db.refresh(settings)
    return settings


# =============================================================================
# READ OPERATION TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.email
class TestVendorEmailSettingsRead:
    """Test suite for reading email settings."""

    def test_get_settings_exists(self, db, test_email_settings):
        """Test getting settings when they exist."""
        settings = vendor_email_settings_service.get_settings(db, test_email_settings.vendor_id)

        assert settings is not None
        assert settings.from_email == "test@example.com"
        assert settings.provider == "smtp"

    def test_get_settings_not_exists(self, db, test_vendor):
        """Test getting settings when they don't exist."""
        settings = vendor_email_settings_service.get_settings(db, test_vendor.id)

        assert settings is None

    def test_get_settings_or_404_exists(self, db, test_email_settings):
        """Test get_settings_or_404 when settings exist."""
        settings = vendor_email_settings_service.get_settings_or_404(db, test_email_settings.vendor_id)

        assert settings is not None
        assert settings.id == test_email_settings.id

    def test_get_settings_or_404_not_exists(self, db, test_vendor):
        """Test get_settings_or_404 raises exception when not found."""
        with pytest.raises(ResourceNotFoundException) as exc:
            vendor_email_settings_service.get_settings_or_404(db, test_vendor.id)

        assert "vendor_email_settings" in str(exc.value)

    def test_is_configured_true(self, db, test_email_settings):
        """Test is_configured returns True for configured settings."""
        result = vendor_email_settings_service.is_configured(db, test_email_settings.vendor_id)

        assert result is True

    def test_is_configured_false_not_exists(self, db, test_vendor):
        """Test is_configured returns False when settings don't exist."""
        result = vendor_email_settings_service.is_configured(db, test_vendor.id)

        assert result is False

    def test_get_status_configured(self, db, test_email_settings):
        """Test get_status for configured settings."""
        status = vendor_email_settings_service.get_status(db, test_email_settings.vendor_id)

        assert status["is_configured"] is True
        assert status["is_verified"] is False
        assert status["provider"] == "smtp"
        assert status["from_email"] == "test@example.com"

    def test_get_status_not_configured(self, db, test_vendor):
        """Test get_status when settings don't exist."""
        status = vendor_email_settings_service.get_status(db, test_vendor.id)

        assert status["is_configured"] is False
        assert status["is_verified"] is False
        assert status["provider"] is None


# =============================================================================
# WRITE OPERATION TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.email
class TestVendorEmailSettingsWrite:
    """Test suite for writing email settings."""

    def test_create_settings(self, db, test_vendor):
        """Test creating new email settings."""
        data = {
            "from_email": "new@example.com",
            "from_name": "New Sender",
            "provider": "smtp",
            "smtp_host": "smtp.example.com",
            "smtp_port": 587,
            "smtp_username": "user",
            "smtp_password": "pass",
        }

        settings = vendor_email_settings_service.create_or_update(
            db=db,
            vendor_id=test_vendor.id,
            data=data,
            current_tier=TierCode.ESSENTIAL,
        )

        assert settings.from_email == "new@example.com"
        assert settings.provider == "smtp"
        assert settings.smtp_host == "smtp.example.com"

    def test_update_existing_settings(self, db, test_email_settings):
        """Test updating existing settings."""
        data = {
            "from_email": "updated@example.com",
            "from_name": "Updated Sender",
        }

        settings = vendor_email_settings_service.create_or_update(
            db=db,
            vendor_id=test_email_settings.vendor_id,
            data=data,
            current_tier=TierCode.ESSENTIAL,
        )

        assert settings.from_email == "updated@example.com"
        assert settings.from_name == "Updated Sender"
        # Other fields should remain unchanged
        assert settings.smtp_host == "smtp.example.com"

    def test_premium_provider_requires_business_tier(self, db, test_vendor):
        """Test that premium providers require Business tier."""
        data = {
            "from_email": "test@example.com",
            "from_name": "Test",
            "provider": "sendgrid",
            "sendgrid_api_key": "test-key",
        }

        with pytest.raises(AuthorizationException) as exc:
            vendor_email_settings_service.create_or_update(
                db=db,
                vendor_id=test_vendor.id,
                data=data,
                current_tier=TierCode.ESSENTIAL,
            )

        assert "Business or Enterprise" in str(exc.value)

    def test_premium_provider_allowed_for_business(self, db, test_vendor):
        """Test that premium providers work with Business tier."""
        data = {
            "from_email": "test@example.com",
            "from_name": "Test",
            "provider": "sendgrid",
            "sendgrid_api_key": "test-key",
        }

        settings = vendor_email_settings_service.create_or_update(
            db=db,
            vendor_id=test_vendor.id,
            data=data,
            current_tier=TierCode.BUSINESS,
        )

        assert settings.provider == "sendgrid"

    def test_provider_change_resets_verification(self, db, test_verified_email_settings):
        """Test that changing provider resets verification status."""
        assert test_verified_email_settings.is_verified is True

        data = {"smtp_host": "new-smtp.example.com"}

        settings = vendor_email_settings_service.create_or_update(
            db=db,
            vendor_id=test_verified_email_settings.vendor_id,
            data=data,
            current_tier=TierCode.ESSENTIAL,
        )

        assert settings.is_verified is False

    def test_delete_settings(self, db, test_email_settings):
        """Test deleting email settings."""
        vendor_id = test_email_settings.vendor_id

        vendor_email_settings_service.delete(db, vendor_id)
        db.commit()

        # Verify deletion
        settings = vendor_email_settings_service.get_settings(db, vendor_id)
        assert settings is None

    def test_delete_settings_not_found(self, db, test_vendor):
        """Test deleting non-existent settings raises exception."""
        with pytest.raises(ResourceNotFoundException):
            vendor_email_settings_service.delete(db, test_vendor.id)


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


@pytest.mark.unit
@pytest.mark.email
class TestVendorEmailSettingsVerification:
    """Test suite for email verification."""

    def test_verify_settings_not_configured(self, db, test_vendor):
        """Test verification fails for non-existent settings."""
        with pytest.raises(ResourceNotFoundException):
            vendor_email_settings_service.verify_settings(db, test_vendor.id, "test@example.com")

    def test_verify_settings_incomplete(self, db, test_vendor):
        """Test verification fails for incomplete settings."""
        # Create incomplete settings
        settings = VendorEmailSettings(
            vendor_id=test_vendor.id,
            from_email="test@example.com",
            from_name="Test",
            provider="smtp",
            # Missing SMTP config
            is_configured=False,
        )
        db.add(settings)
        db.commit()

        with pytest.raises(ValidationException) as exc:
            vendor_email_settings_service.verify_settings(db, test_vendor.id, "test@example.com")

        assert "incomplete" in str(exc.value).lower()

    @patch("smtplib.SMTP")
    def test_verify_smtp_success(self, mock_smtp, db, test_email_settings):
        """Test successful SMTP verification."""
        # Mock SMTP connection
        mock_server = MagicMock()
        mock_smtp.return_value = mock_server

        result = vendor_email_settings_service.verify_settings(
            db,
            test_email_settings.vendor_id,
            "recipient@example.com",
        )

        assert result["success"] is True
        assert "successfully" in result["message"].lower()

    @patch("smtplib.SMTP")
    def test_verify_smtp_failure(self, mock_smtp, db, test_email_settings):
        """Test SMTP verification failure."""
        # Mock SMTP error
        mock_smtp.side_effect = Exception("Connection refused")

        result = vendor_email_settings_service.verify_settings(
            db,
            test_email_settings.vendor_id,
            "recipient@example.com",
        )

        assert result["success"] is False
        assert "failed" in result["message"].lower()


# =============================================================================
# PROVIDER AVAILABILITY TESTS
# =============================================================================


@pytest.mark.unit
@pytest.mark.email
class TestVendorEmailProvidersAvailability:
    """Test suite for provider availability checking."""

    def test_get_providers_essential_tier(self, db):
        """Test available providers for Essential tier."""
        providers = vendor_email_settings_service.get_available_providers(TierCode.ESSENTIAL)

        # Find SMTP provider
        smtp = next((p for p in providers if p["code"] == "smtp"), None)
        assert smtp is not None
        assert smtp["available"] is True

        # Find SendGrid provider
        sendgrid = next((p for p in providers if p["code"] == "sendgrid"), None)
        assert sendgrid is not None
        assert sendgrid["available"] is False

    def test_get_providers_business_tier(self, db):
        """Test available providers for Business tier."""
        providers = vendor_email_settings_service.get_available_providers(TierCode.BUSINESS)

        # All providers should be available
        for provider in providers:
            assert provider["available"] is True

    def test_get_providers_no_tier(self, db):
        """Test available providers with no subscription."""
        providers = vendor_email_settings_service.get_available_providers(None)

        # Only SMTP should be available
        smtp = next((p for p in providers if p["code"] == "smtp"), None)
        assert smtp["available"] is True

        sendgrid = next((p for p in providers if p["code"] == "sendgrid"), None)
        assert sendgrid["available"] is False
