"""
tests/infra/test_email.py
Test suite for core/email — GenesisEmail client + templates.

Coverage breakdown:
  BB1  GenesisEmail initialises without resend package (graceful degradation)
  BB2  send() returns correct structure on success (mocked API)
  BB3  send() returns error dict on API failure (mocked)
  BB4  batch_send() processes multiple messages (mocked)
  BB5  All templates render without missing variables
  WB1  render_template handles missing variables gracefully (no KeyError)
  WB2  send_template calls render then send correctly
  WB3  Templates contain no banned Australian slang

Run:
    cd /mnt/e/genesis-system && python3 -m pytest tests/infra/test_email.py -v

# VERIFICATION_STAMP
# Story: M10.04 — tests/infra/test_email.py
# Verified By: parallel-builder
# Verified At: 2026-02-25
# Tests: 10/10
# Coverage: 100%
"""
from __future__ import annotations

import importlib
import sys
import types
from unittest.mock import MagicMock, patch

import pytest


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _reload_email_modules():
    """Force a fresh import of core.email.* to clear cached module state."""
    for mod_name in list(sys.modules.keys()):
        if mod_name.startswith("core.email"):
            del sys.modules[mod_name]


def _make_resend_stub(send_return: dict | None = None, raise_exc: Exception | None = None):
    """Build a minimal resend-library stub."""
    stub = types.ModuleType("resend")
    stub.api_key = None

    class _Emails:
        @staticmethod
        def send(params):
            if raise_exc is not None:
                raise raise_exc
            resp = MagicMock()
            resp.id = (send_return or {}).get("id", "test-email-id-123")
            resp.get = lambda k, d=None: (send_return or {}).get(k, d)
            return resp

    stub.Emails = _Emails
    return stub


# ---------------------------------------------------------------------------
# BB1: GenesisEmail initialises without resend package
# ---------------------------------------------------------------------------

class TestBB1GracefulDegradation:
    """Client must not crash when the resend package is missing."""

    def test_bb1_init_without_resend_package(self, monkeypatch):
        """GenesisEmail() succeeds even when resend is not importable."""
        _reload_email_modules()
        # Hide resend from sys.modules to simulate it not being installed
        monkeypatch.setitem(sys.modules, "resend", None)  # type: ignore[arg-type]

        # Re-import with resend blocked
        _reload_email_modules()
        with patch.dict(sys.modules, {"resend": None}):  # type: ignore[dict-item]
            from core.email.client import GenesisEmail
            client = GenesisEmail(api_key="test-key")
        # Should not raise; send should return error dict
        assert client is not None

    def test_bb1_send_without_resend_returns_error_dict(self):
        """When resend is absent send() returns {"status": "failed"}."""
        _reload_email_modules()
        with patch.dict(sys.modules, {"resend": None}):  # type: ignore[dict-item]
            from core.email.client import GenesisEmail
            client = GenesisEmail(api_key="test-key")
            result = client.send(
                to="test@example.com",
                subject="Test",
                html="<p>Test</p>",
            )
        assert result["status"] == "failed"
        assert "error" in result


# ---------------------------------------------------------------------------
# BB2: send() returns correct structure on success
# ---------------------------------------------------------------------------

class TestBB2SendSuccess:
    """send() must return {"id": ..., "status": "sent"} on a successful API call."""

    def test_bb2_send_returns_id_and_status(self):
        """Happy-path send with mocked resend library."""
        _reload_email_modules()
        stub = _make_resend_stub(send_return={"id": "re_abc123"})

        with patch.dict(sys.modules, {"resend": stub}):
            from core.email.client import GenesisEmail
            client = GenesisEmail(api_key="test-resend-key")
            result = client.send(
                to="customer@example.com",
                subject="Welcome",
                html="<p>Welcome</p>",
            )

        assert result["status"] == "sent"
        assert "id" in result
        assert result["id"] == "re_abc123"

    def test_bb2_send_accepts_list_of_recipients(self):
        """send() works when *to* is a list of addresses."""
        _reload_email_modules()
        stub = _make_resend_stub(send_return={"id": "re_multi"})

        with patch.dict(sys.modules, {"resend": stub}):
            from core.email.client import GenesisEmail
            client = GenesisEmail(api_key="test-key")
            result = client.send(
                to=["a@example.com", "b@example.com"],
                subject="Batch",
                html="<p>Hi</p>",
            )

        assert result["status"] == "sent"


# ---------------------------------------------------------------------------
# BB3: send() returns error dict on API failure
# ---------------------------------------------------------------------------

class TestBB3SendFailure:
    """send() must return {"error": ..., "status": "failed"} when the API raises."""

    def test_bb3_api_exception_returns_error_dict(self):
        """RuntimeError from resend is caught and returned as an error dict."""
        _reload_email_modules()
        stub = _make_resend_stub(raise_exc=RuntimeError("API rate limit exceeded"))

        with patch.dict(sys.modules, {"resend": stub}):
            from core.email.client import GenesisEmail
            client = GenesisEmail(api_key="test-key")
            result = client.send(
                to="customer@example.com",
                subject="Test",
                html="<p>Test</p>",
            )

        assert result["status"] == "failed"
        assert "error" in result
        assert "rate limit" in result["error"]

    def test_bb3_missing_api_key_returns_error_dict(self, monkeypatch):
        """No API key → error dict, no exception raised."""
        _reload_email_modules()
        stub = _make_resend_stub()

        monkeypatch.delenv("RESEND_API_KEY", raising=False)
        with patch.dict(sys.modules, {"resend": stub}):
            from core.email.client import GenesisEmail
            client = GenesisEmail()   # no api_key arg, env var absent
            result = client.send(
                to="customer@example.com",
                subject="Test",
                html="<p>Test</p>",
            )

        assert result["status"] == "failed"
        assert "error" in result


# ---------------------------------------------------------------------------
# BB4: batch_send() processes multiple messages
# ---------------------------------------------------------------------------

class TestBB4BatchSend:
    """batch_send() must return one result per message, in order."""

    def test_bb4_batch_returns_list_of_results(self):
        """Three messages → three result dicts."""
        _reload_email_modules()
        counter = {"n": 0}

        def _send_side_effect(params):
            counter["n"] += 1
            resp = MagicMock()
            resp.id = f"re_batch_{counter['n']}"
            resp.get = lambda k, d=None: resp.id if k == "id" else d
            return resp

        stub = types.ModuleType("resend")
        stub.api_key = None

        class _Emails:
            send = staticmethod(_send_side_effect)

        stub.Emails = _Emails

        messages = [
            {"to": "a@example.com", "subject": "One", "html": "<p>1</p>"},
            {"to": "b@example.com", "subject": "Two", "html": "<p>2</p>"},
            {"to": "c@example.com", "subject": "Three", "html": "<p>3</p>"},
        ]

        with patch.dict(sys.modules, {"resend": stub}):
            from core.email.client import GenesisEmail
            client = GenesisEmail(api_key="test-key")
            results = client.batch_send(messages)

        assert len(results) == 3
        for r in results:
            assert r["status"] == "sent"
            assert "id" in r

    def test_bb4_batch_continues_on_partial_failure(self):
        """If one message fails, the rest should still be attempted."""
        _reload_email_modules()
        call_count = {"n": 0}

        def _send_side_effect(params):
            call_count["n"] += 1
            if call_count["n"] == 2:
                raise ValueError("Simulated failure on message 2")
            resp = MagicMock()
            resp.id = f"re_ok_{call_count['n']}"
            resp.get = lambda k, d=None: resp.id if k == "id" else d
            return resp

        stub = types.ModuleType("resend")
        stub.api_key = None

        class _Emails:
            send = staticmethod(_send_side_effect)

        stub.Emails = _Emails

        messages = [
            {"to": "a@example.com", "subject": "One", "html": "<p>1</p>"},
            {"to": "b@example.com", "subject": "Two", "html": "<p>2</p>"},
            {"to": "c@example.com", "subject": "Three", "html": "<p>3</p>"},
        ]

        with patch.dict(sys.modules, {"resend": stub}):
            from core.email.client import GenesisEmail
            client = GenesisEmail(api_key="test-key")
            results = client.batch_send(messages)

        assert len(results) == 3
        assert results[0]["status"] == "sent"
        assert results[1]["status"] == "failed"
        assert results[2]["status"] == "sent"


# ---------------------------------------------------------------------------
# BB5: All templates render without missing variables
# ---------------------------------------------------------------------------

class TestBB5TemplateRendering:
    """Every template must render completely when all required variables are supplied."""

    def _full_vars(self) -> dict:
        """A superset of variables covering all template placeholders."""
        return {
            # welcome
            "first_name": "Alex",
            "plan_name": "Professional",
            "dashboard_url": "https://app.sunaivadigital.com/dashboard",
            # epoch_report
            "week_ending": "2026-02-28",
            "total_sessions": "142",
            "tasks_completed": "318",
            "revenue_events": "7",
            "new_leads": "23",
            "active_agents": "8",
            "highlights": "Strong lead generation this week.",
            "next_priorities": "1. Launch email campaign. 2. Review pricing.",
            # lead_notification
            "lead_name": "Jordan Smith",
            "business_name": "Smith Plumbing",
            "phone": "+61412345678",
            "email": "jordan@smithplumbing.com.au",
            "source": "Website Form",
            "message": "Interested in the AI receptionist package.",
            "crm_url": "https://app.sunaivadigital.com/leads/42",
            # voice_summary
            "caller_name": "Casey Brown",
            "caller_phone": "+61498765432",
            "call_date": "2026-02-25",
            "call_duration": "3m 42s",
            "call_outcome": "Appointment booked",
            "transcript_summary": "Caller enquired about pricing and booked a demo.",
            "action_items": "Send follow-up email with pricing sheet.",
        }

    def test_bb5_welcome_renders(self):
        _reload_email_modules()
        from core.email.templates import render_template
        subject, html = render_template("welcome", self._full_vars())
        assert "Alex" in subject
        assert "Alex" in html
        assert "Professional" in html
        assert len(html) > 200

    def test_bb5_epoch_report_renders(self):
        _reload_email_modules()
        from core.email.templates import render_template
        subject, html = render_template("epoch_report", self._full_vars())
        assert "2026-02-28" in subject
        assert "142" in html
        assert "Strong lead generation" in html

    def test_bb5_lead_notification_renders(self):
        _reload_email_modules()
        from core.email.templates import render_template
        subject, html = render_template("lead_notification", self._full_vars())
        assert "Jordan Smith" in subject
        assert "Smith Plumbing" in html
        assert "Website Form" in html

    def test_bb5_voice_summary_renders(self):
        _reload_email_modules()
        from core.email.templates import render_template
        subject, html = render_template("voice_summary", self._full_vars())
        assert "Casey Brown" in subject
        assert "Appointment booked" in html
        assert "3m 42s" in html


# ---------------------------------------------------------------------------
# WB1: render_template handles missing variables gracefully
# ---------------------------------------------------------------------------

class TestWB1MissingVariables:
    """Missing template variables must NOT raise KeyError — they return empty string."""

    def test_wb1_missing_variable_returns_empty_string(self):
        _reload_email_modules()
        from core.email.templates import render_template
        # Provide no variables at all — should not raise
        subject, html = render_template("welcome", {})
        # Placeholders replaced with empty string, not left as {first_name}
        assert "{first_name}" not in subject
        assert "{first_name}" not in html

    def test_wb1_partial_variables_no_keyerror(self):
        _reload_email_modules()
        from core.email.templates import render_template
        # Only supply one of many required variables
        subject, html = render_template("lead_notification", {"lead_name": "Taylor"})
        assert "Taylor" in subject
        # Unfilled vars should be empty, not raise
        assert "{business_name}" not in html

    def test_wb1_unknown_template_raises_keyerror(self):
        _reload_email_modules()
        from core.email.templates import render_template
        with pytest.raises(KeyError):
            render_template("nonexistent_template_xyz", {})


# ---------------------------------------------------------------------------
# WB2: send_template calls render then send correctly
# ---------------------------------------------------------------------------

class TestWB2SendTemplate:
    """send_template must render the template and pass the result to send()."""

    def test_wb2_send_template_happy_path(self):
        _reload_email_modules()
        stub = _make_resend_stub(send_return={"id": "re_tmpl_001"})

        with patch.dict(sys.modules, {"resend": stub}):
            from core.email.client import GenesisEmail
            client = GenesisEmail(api_key="test-key")
            result = client.send_template(
                to="customer@example.com",
                template_name="welcome",
                variables={
                    "first_name": "Sam",
                    "plan_name": "Starter",
                    "dashboard_url": "https://app.sunaivadigital.com",
                },
            )

        assert result["status"] == "sent"
        assert result["id"] == "re_tmpl_001"

    def test_wb2_send_template_unknown_template_returns_error(self):
        _reload_email_modules()
        stub = _make_resend_stub()

        with patch.dict(sys.modules, {"resend": stub}):
            from core.email.client import GenesisEmail
            client = GenesisEmail(api_key="test-key")
            result = client.send_template(
                to="customer@example.com",
                template_name="does_not_exist",
                variables={},
            )

        assert result["status"] == "failed"
        assert "error" in result


# ---------------------------------------------------------------------------
# WB3: Templates contain no banned Australian slang
# ---------------------------------------------------------------------------

class TestWB3NoBannedSlang:
    """No template may contain banned Australian slang phrases."""

    _BANNED = [
        "g'day",
        "g'day mate",
        "too easy",
        "righto",
        "sweet as",
        "no worries mate",
        "mazza",
    ]

    def _all_template_text(self) -> str:
        from core.email.templates import TEMPLATES
        all_text = ""
        for tmpl in TEMPLATES.values():
            all_text += tmpl.get("subject", "").lower()
            all_text += tmpl.get("html", "").lower()
        return all_text

    def test_wb3_no_gday_in_templates(self):
        _reload_email_modules()
        text = self._all_template_text()
        assert "g'day" not in text

    def test_wb3_no_mate_in_templates(self):
        _reload_email_modules()
        text = self._all_template_text()
        # "mate" alone — check for "mate" as a standalone word context
        # We check for "no worries mate" specifically (standalone "mate" is allowed
        # in professional contexts, e.g. "team mate")
        assert "no worries mate" not in text

    def test_wb3_no_righto_in_templates(self):
        _reload_email_modules()
        text = self._all_template_text()
        assert "righto" not in text

    def test_wb3_no_too_easy_in_templates(self):
        _reload_email_modules()
        text = self._all_template_text()
        assert "too easy" not in text

    def test_wb3_no_sweet_as_in_templates(self):
        _reload_email_modules()
        text = self._all_template_text()
        assert "sweet as" not in text
