# tests/unit/services/test_email_service.py
"""Unit tests for EmailService - email sending and template rendering."""

import json
from unittest.mock import MagicMock, patch

import pytest

from app.modules.messaging.models import (
    EmailCategory,
    EmailLog,
    EmailStatus,
    EmailTemplate,
)
from app.modules.messaging.services.email_service import (
    DebugProvider,
    EmailService,
    SMTPProvider,
    get_provider,
)


@pytest.mark.unit
@pytest.mark.email
class TestEmailProviders:
    """Test suite for email providers."""

    def test_debug_provider_send(self):
        """Test DebugProvider logs instead of sending."""
        provider = DebugProvider()

        success, message_id, error = provider.send(
            to_email="test@example.com",
            to_name="Test User",
            subject="Test Subject",
            body_html="<h1>Hello</h1>",
            body_text="Hello",
            from_email="noreply@orion.lu",
            from_name="Orion",
        )

        assert success is True
        assert message_id == "debug-test@example.com"
        assert error is None

    def test_debug_provider_with_reply_to(self):
        """Test DebugProvider with reply-to header."""
        provider = DebugProvider()

        success, message_id, error = provider.send(
            to_email="test@example.com",
            to_name="Test User",
            subject="Test Subject",
            body_html="<h1>Hello</h1>",
            body_text=None,
            from_email="noreply@orion.lu",
            from_name="Orion",
            reply_to="support@orion.lu",
        )

        assert success is True

    @patch("app.modules.messaging.services.email_service.settings")
    def test_get_provider_debug_mode(self, mock_settings):
        """Test get_provider returns DebugProvider in debug mode."""
        mock_settings.email_debug = True

        provider = get_provider()

        assert isinstance(provider, DebugProvider)

    @patch("app.modules.messaging.services.email_service.settings")
    def test_get_provider_smtp(self, mock_settings):
        """Test get_provider returns SMTPProvider for smtp config."""
        mock_settings.email_debug = False
        mock_settings.email_provider = "smtp"

        provider = get_provider()

        assert isinstance(provider, SMTPProvider)

    @patch("app.modules.messaging.services.email_service.settings")
    def test_get_provider_unknown_defaults_to_smtp(self, mock_settings):
        """Test get_provider defaults to SMTP for unknown providers."""
        mock_settings.email_debug = False
        mock_settings.email_provider = "unknown_provider"

        provider = get_provider()

        assert isinstance(provider, SMTPProvider)


@pytest.mark.unit
@pytest.mark.email
class TestEmailService:
    """Test suite for EmailService."""

    def test_render_template_simple(self, db):
        """Test simple template rendering."""
        service = EmailService(db)

        result = service.render_template(
            "Hello {{ name }}!", {"name": "World"}
        )

        assert result == "Hello World!"

    def test_render_template_multiple_vars(self, db):
        """Test template rendering with multiple variables."""
        service = EmailService(db)

        result = service.render_template(
            "Hi {{ first_name }}, your code is {{ store_code }}.",
            {"first_name": "John", "store_code": "ACME"}
        )

        assert result == "Hi John, your code is ACME."

    def test_render_template_missing_var(self, db):
        """Test template rendering with missing variable returns empty."""
        service = EmailService(db)

        result = service.render_template(
            "Hello {{ name }}!",
            {}  # No name provided
        )

        # Jinja2 renders missing vars as empty string by default
        assert "Hello" in result

    def test_render_template_error_returns_original(self, db):
        """Test template rendering error returns original string."""
        service = EmailService(db)

        # Invalid Jinja2 syntax
        template = "Hello {{ name"
        result = service.render_template(template, {"name": "World"})

        assert result == template

    def test_get_template_not_found(self, db):
        """Test get_template returns None for non-existent template."""
        service = EmailService(db)

        result = service.get_template("nonexistent_template", "en")

        assert result is None

    def test_get_template_with_language_fallback(self, db):
        """Test get_template falls back to English."""
        # Create English template only
        template = EmailTemplate(
            code="test_template",
            language="en",
            name="Test Template",
            subject="Test",
            body_html="<p>Test</p>",
            category=EmailCategory.SYSTEM.value,
        )
        db.add(template)
        db.commit()

        service = EmailService(db)

        # Request German, should fallback to English
        result = service.get_template("test_template", "de")

        assert result is not None
        assert result.language == "en"

        # Cleanup
        db.delete(template)
        db.commit()

    def test_get_template_specific_language(self, db):
        """Test get_template returns specific language if available."""
        # Create templates in both languages
        template_en = EmailTemplate(
            code="test_lang_template",
            language="en",
            name="Test Template EN",
            subject="English Subject",
            body_html="<p>English</p>",
            category=EmailCategory.SYSTEM.value,
        )
        template_fr = EmailTemplate(
            code="test_lang_template",
            language="fr",
            name="Test Template FR",
            subject="French Subject",
            body_html="<p>Français</p>",
            category=EmailCategory.SYSTEM.value,
        )
        db.add(template_en)
        db.add(template_fr)
        db.commit()

        service = EmailService(db)

        # Request French
        result = service.get_template("test_lang_template", "fr")

        assert result is not None
        assert result.language == "fr"
        assert result.subject == "French Subject"

        # Cleanup
        db.delete(template_en)
        db.delete(template_fr)
        db.commit()


@pytest.mark.unit
@pytest.mark.email
class TestEmailSending:
    """Test suite for email sending functionality."""

    @patch("app.modules.messaging.services.email_service.get_platform_provider")
    @patch("app.modules.messaging.services.email_service.get_platform_email_config")
    def test_send_raw_success(self, mock_get_config, mock_get_platform_provider, db):
        """Test successful raw email sending."""
        # Setup mocks
        mock_get_config.return_value = {
            "enabled": True,
            "debug": False,
            "provider": "smtp",
            "from_email": "noreply@test.com",
            "from_name": "Test",
            "reply_to": "",
        }

        mock_provider = MagicMock()
        mock_provider.send.return_value = (True, "msg-123", None)
        mock_get_platform_provider.return_value = mock_provider

        service = EmailService(db)

        log = service.send_raw(
            to_email="user@example.com",
            to_name="User",
            subject="Test Subject",
            body_html="<h1>Hello</h1>",
        )

        assert log.status == EmailStatus.SENT.value
        assert log.recipient_email == "user@example.com"
        assert log.subject == "Test Subject"
        assert log.provider_message_id == "msg-123"

    @patch("app.modules.messaging.services.email_service.get_platform_provider")
    @patch("app.modules.messaging.services.email_service.get_platform_email_config")
    def test_send_raw_failure(self, mock_get_config, mock_get_platform_provider, db):
        """Test failed raw email sending."""
        # Setup mocks
        mock_get_config.return_value = {
            "enabled": True,
            "debug": False,
            "provider": "smtp",
            "from_email": "noreply@test.com",
            "from_name": "Test",
            "reply_to": "",
        }

        mock_provider = MagicMock()
        mock_provider.send.return_value = (False, None, "Connection refused")
        mock_get_platform_provider.return_value = mock_provider

        service = EmailService(db)

        log = service.send_raw(
            to_email="user@example.com",
            subject="Test Subject",
            body_html="<h1>Hello</h1>",
        )

        assert log.status == EmailStatus.FAILED.value
        assert log.error_message == "Connection refused"

    @patch("app.modules.messaging.services.email_service.settings")
    def test_send_raw_email_disabled(self, mock_settings, db):
        """Test email sending when disabled."""
        mock_settings.email_enabled = False
        mock_settings.email_from_address = "noreply@test.com"
        mock_settings.email_from_name = "Test"
        mock_settings.email_reply_to = ""
        mock_settings.email_provider = "smtp"
        mock_settings.email_debug = False

        service = EmailService(db)

        log = service.send_raw(
            to_email="user@example.com",
            subject="Test Subject",
            body_html="<h1>Hello</h1>",
        )

        assert log.status == EmailStatus.FAILED.value
        assert "disabled" in log.error_message.lower()

    @patch("app.modules.messaging.services.email_service.get_platform_provider")
    @patch("app.modules.messaging.services.email_service.get_platform_email_config")
    def test_send_template_success(self, mock_get_config, mock_get_platform_provider, db):
        """Test successful template email sending."""
        # Create test template
        template = EmailTemplate(
            code="test_send_template",
            language="en",
            name="Test Send Template",
            subject="Hello {{ first_name }}",
            body_html="<p>Welcome {{ first_name }} to {{ merchant }}</p>",
            body_text="Welcome {{ first_name }} to {{ merchant }}",
            category=EmailCategory.SYSTEM.value,
        )
        db.add(template)
        db.commit()

        # Setup mocks
        mock_get_config.return_value = {
            "enabled": True,
            "debug": False,
            "provider": "smtp",
            "from_email": "noreply@test.com",
            "from_name": "Test",
            "reply_to": "",
        }

        mock_provider = MagicMock()
        mock_provider.send.return_value = (True, "msg-456", None)
        mock_get_platform_provider.return_value = mock_provider

        service = EmailService(db)

        log = service.send_template(
            template_code="test_send_template",
            to_email="user@example.com",
            language="en",
            variables={
                "first_name": "John",
                "merchant": "ACME Corp"
            },
        )

        assert log.status == EmailStatus.SENT.value
        assert log.template_code == "test_send_template"
        assert log.subject == "Hello John"

        # Cleanup - delete log first due to FK constraint
        db.delete(log)
        db.delete(template)
        db.commit()

    def test_send_template_not_found(self, db):
        """Test sending with non-existent template."""
        service = EmailService(db)

        log = service.send_template(
            template_code="nonexistent_template",
            to_email="user@example.com",
        )

        assert log.status == EmailStatus.FAILED.value
        assert "not found" in log.error_message.lower()


@pytest.mark.unit
@pytest.mark.email
class TestEmailLog:
    """Test suite for EmailLog model methods."""

    def test_mark_sent(self, db):
        """Test EmailLog.mark_sent method."""
        log = EmailLog(
            recipient_email="test@example.com",
            subject="Test",
            from_email="noreply@test.com",
            status=EmailStatus.PENDING.value,
        )
        db.add(log)
        db.flush()

        log.mark_sent("provider-msg-id")

        assert log.status == EmailStatus.SENT.value
        assert log.sent_at is not None
        assert log.provider_message_id == "provider-msg-id"

        db.rollback()

    def test_mark_failed(self, db):
        """Test EmailLog.mark_failed method."""
        log = EmailLog(
            recipient_email="test@example.com",
            subject="Test",
            from_email="noreply@test.com",
            status=EmailStatus.PENDING.value,
            retry_count=0,
        )
        db.add(log)
        db.flush()

        log.mark_failed("Connection timeout")

        assert log.status == EmailStatus.FAILED.value
        assert log.error_message == "Connection timeout"
        assert log.retry_count == 1

        db.rollback()

    def test_mark_delivered(self, db):
        """Test EmailLog.mark_delivered method."""
        log = EmailLog(
            recipient_email="test@example.com",
            subject="Test",
            from_email="noreply@test.com",
            status=EmailStatus.SENT.value,
        )
        db.add(log)
        db.flush()

        log.mark_delivered()

        assert log.status == EmailStatus.DELIVERED.value
        assert log.delivered_at is not None

        db.rollback()

    def test_mark_opened(self, db):
        """Test EmailLog.mark_opened method."""
        log = EmailLog(
            recipient_email="test@example.com",
            subject="Test",
            from_email="noreply@test.com",
            status=EmailStatus.DELIVERED.value,
        )
        db.add(log)
        db.flush()

        log.mark_opened()

        assert log.status == EmailStatus.OPENED.value
        assert log.opened_at is not None

        db.rollback()


@pytest.mark.unit
@pytest.mark.email
class TestEmailTemplate:
    """Test suite for EmailTemplate model."""

    def test_variables_list_property(self, db):
        """Test EmailTemplate.variables_list property."""
        template = EmailTemplate(
            code="test_vars",
            language="en",
            name="Test",
            subject="Test",
            body_html="<p>Test</p>",
            category=EmailCategory.SYSTEM.value,
            variables=json.dumps(["first_name", "last_name", "email"]),
        )
        db.add(template)
        db.flush()

        assert template.variables_list == ["first_name", "last_name", "email"]

        db.rollback()

    def test_variables_list_empty(self, db):
        """Test EmailTemplate.variables_list with no variables."""
        template = EmailTemplate(
            code="test_no_vars",
            language="en",
            name="Test",
            subject="Test",
            body_html="<p>Test</p>",
            category=EmailCategory.SYSTEM.value,
            variables=None,
        )
        db.add(template)
        db.flush()

        assert template.variables_list == []

        db.rollback()

    def test_variables_list_invalid_json(self, db):
        """Test EmailTemplate.variables_list with invalid JSON."""
        template = EmailTemplate(
            code="test_invalid_json",
            language="en",
            name="Test",
            subject="Test",
            body_html="<p>Test</p>",
            category=EmailCategory.SYSTEM.value,
            variables="not valid json",
        )
        db.add(template)
        db.flush()

        assert template.variables_list == []

        db.rollback()

    def test_template_repr(self, db):
        """Test EmailTemplate string representation."""
        template = EmailTemplate(
            code="signup_welcome",
            language="en",
            name="Welcome",
            subject="Welcome",
            body_html="<p>Welcome</p>",
            category=EmailCategory.AUTH.value,
        )

        assert "signup_welcome" in repr(template)
        assert "en" in repr(template)


@pytest.mark.unit
@pytest.mark.email
class TestSignupWelcomeEmail:
    """Test suite for signup welcome email integration."""

    @pytest.fixture
    def welcome_template(self, db):
        """Create a welcome template for testing."""
        import json

        template = EmailTemplate(
            code="signup_welcome",
            language="en",
            name="Signup Welcome",
            subject="Welcome {{ first_name }}!",
            body_html="<p>Welcome {{ first_name }} to {{ merchant_name }}</p>",
            body_text="Welcome {{ first_name }} to {{ merchant_name }}",
            category=EmailCategory.AUTH.value,
            variables=json.dumps([
                "first_name", "merchant_name", "email", "store_code",
                "login_url", "trial_days", "tier_name"
            ]),
        )
        db.add(template)
        db.commit()

        yield template

        # Cleanup - delete email logs referencing this template first
        db.query(EmailLog).filter(EmailLog.template_id == template.id).delete()
        db.delete(template)
        db.commit()

    def test_welcome_template_rendering(self, db, welcome_template):
        """Test that welcome template renders correctly."""
        service = EmailService(db)

        template = service.get_template("signup_welcome", "en")
        assert template is not None
        assert template.code == "signup_welcome"

        # Test rendering
        rendered = service.render_template(
            template.subject,
            {"first_name": "John"}
        )
        assert rendered == "Welcome John!"

    def test_welcome_template_has_required_variables(self, db, welcome_template):
        """Test welcome template has all required variables."""
        template = (
            db.query(EmailTemplate)
            .filter(
                EmailTemplate.code == "signup_welcome",
                EmailTemplate.language == "en",
            )
            .first()
        )

        assert template is not None

        required_vars = [
            "first_name",
            "merchant_name",
            "store_code",
            "login_url",
            "trial_days",
            "tier_name",
        ]

        for var in required_vars:
            assert var in template.variables_list, f"Missing variable: {var}"

    # test_welcome_email_send removed — depends on subscription service methods that were refactored
