"""A connected Mailchimp account: reading it, and sending through it.

Fully isolated from any real database or network call -- get_db_connection
and httpx.post are mocked in every test.
"""
from unittest.mock import MagicMock, patch

import httpx
import pytest

from app.services.integrations.mailchimp import get_mailchimp_connection, send_via_mandrill


def _mock_row(monkeypatch, row):
    """A fake `SELECT metadata, status FROM {tenant}.integrations ...` result."""
    conn = MagicMock()
    cur = conn.cursor.return_value.__enter__.return_value
    cur.fetchone.return_value = row
    monkeypatch.setattr(
        "app.services.integrations.mailchimp.get_db_connection", lambda: conn)
    return conn


# --- get_mailchimp_connection -------------------------------------------------

def test_no_row_means_not_connected(monkeypatch):
    _mock_row(monkeypatch, None)
    assert get_mailchimp_connection("org_test") is None


def test_a_disconnected_integration_is_not_used(monkeypatch):
    # The row still exists after a tenant disconnects -- status flips instead
    # of the row being deleted (see app/services/integrations/platform.py's
    # set_integration_status for the same pattern on the master-DB table).
    _mock_row(monkeypatch, ({"sms": {"transactionalApiKey": "md-key"}}, "INACTIVE"))
    assert get_mailchimp_connection("org_test") is None


def test_a_connection_with_no_transactional_key_is_not_usable(monkeypatch):
    # A connection made before SMS Mode existed, or one where the tenant never
    # filled in the transactional key field.
    _mock_row(monkeypatch, ({"email": "a@b.com"}, "ACTIVE"))
    assert get_mailchimp_connection("org_test") is None


def test_a_valid_active_connection_is_returned(monkeypatch):
    _mock_row(monkeypatch, ({
        "email": "shivraj@galaxiq.ai",
        "accountName": "GalaxiQ AI",
        "sms": {"transactionalApiKey": "md-abc123"},
    }, "ACTIVE"))

    connection = get_mailchimp_connection("org_test")

    assert connection == {
        "transactional_api_key": "md-abc123",
        "from_email": "shivraj@galaxiq.ai",
        "from_name": "GalaxiQ AI",
    }


def test_a_db_error_is_treated_as_not_connected(monkeypatch):
    conn = MagicMock()
    conn.cursor.side_effect = Exception("connection refused")
    monkeypatch.setattr(
        "app.services.integrations.mailchimp.get_db_connection", lambda: conn)
    assert get_mailchimp_connection("org_test") is None


def test_get_db_connection_itself_failing_is_also_treated_as_not_connected(monkeypatch):
    # get_db_connection() can raise before a connection object even exists
    # (e.g. the DB is unreachable) -- this must not propagate as an
    # unhandled exception up through send_email/email_configured, which now
    # call this on every send with no SMTP fallback left to mask it.
    def boom():
        raise Exception("could not connect to server")
    monkeypatch.setattr(
        "app.services.integrations.mailchimp.get_db_connection", boom)
    assert get_mailchimp_connection("org_test") is None


# --- send_via_mandrill ---------------------------------------------------------

CONNECTION = {
    "transactional_api_key": "md-abc123",
    "from_email": "shivraj@galaxiq.ai",
    "from_name": "GalaxiQ AI",
}


def test_a_successful_send_reports_sent(monkeypatch):
    response = MagicMock()
    response.raise_for_status.return_value = None
    response.json.return_value = [{"email": "user@example.com", "status": "sent"}]
    monkeypatch.setattr("httpx.post", lambda *a, **kw: response)

    result = send_via_mandrill(CONNECTION, "user@example.com", "Hi", "text", "<p>hi</p>")

    assert result == {"status": "sent", "to": "user@example.com", "subject": "Hi"}


def test_queued_and_scheduled_also_count_as_sent(monkeypatch):
    for mandrill_status in ("queued", "scheduled"):
        response = MagicMock()
        response.raise_for_status.return_value = None
        response.json.return_value = [{"status": mandrill_status}]
        monkeypatch.setattr("httpx.post", lambda *a, **kw: response)

        result = send_via_mandrill(CONNECTION, "user@example.com", "Hi", "text", "<p>hi</p>")
        assert result["status"] == "sent"


def test_mandrill_rejecting_the_recipient_is_an_error_not_an_exception(monkeypatch):
    # Mandrill returns HTTP 200 even when it rejects a specific recipient --
    # raise_for_status alone would miss this.
    response = MagicMock()
    response.raise_for_status.return_value = None
    response.json.return_value = [{"status": "rejected", "reject_reason": "invalid"}]
    monkeypatch.setattr("httpx.post", lambda *a, **kw: response)

    result = send_via_mandrill(CONNECTION, "user@example.com", "Hi", "text", "<p>hi</p>")

    assert result["status"] == "error"
    # Generic on purpose: the chat model relays a tool error's detail straight
    # to the customer, so neither the vendor name nor Mandrill's internal
    # reject_reason belongs here.
    assert "mandrill" not in result["detail"].lower()
    assert "invalid" not in result["detail"].lower()


def test_a_network_failure_is_caught_not_raised(monkeypatch):
    def boom(*a, **kw):
        raise httpx.ConnectTimeout("timed out")
    monkeypatch.setattr("httpx.post", boom)

    result = send_via_mandrill(CONNECTION, "user@example.com", "Hi", "text", "<p>hi</p>")

    assert result["status"] == "error"


def test_an_http_error_status_is_caught_not_raised(monkeypatch):
    response = MagicMock()
    response.raise_for_status.side_effect = httpx.HTTPStatusError(
        "401", request=MagicMock(), response=MagicMock())
    monkeypatch.setattr("httpx.post", lambda *a, **kw: response)

    result = send_via_mandrill(CONNECTION, "user@example.com", "Hi", "text", "<p>hi</p>")

    assert result["status"] == "error"


def test_a_reply_to_is_sent_as_a_custom_header(monkeypatch):
    captured = {}

    def fake_post(*args, **kwargs):
        captured["payload"] = kwargs["json"]
        response = MagicMock()
        response.raise_for_status.return_value = None
        response.json.return_value = [{"status": "sent"}]
        return response

    monkeypatch.setattr("httpx.post", fake_post)

    send_via_mandrill(CONNECTION, "user@example.com", "Hi", "text", "<p>hi</p>",
                      reply_to="support@acme.example")

    assert captured["payload"]["message"]["headers"] == {"Reply-To": "support@acme.example"}


def test_no_reply_to_means_no_headers_key(monkeypatch):
    captured = {}

    def fake_post(*args, **kwargs):
        captured["payload"] = kwargs["json"]
        response = MagicMock()
        response.raise_for_status.return_value = None
        response.json.return_value = [{"status": "sent"}]
        return response

    monkeypatch.setattr("httpx.post", fake_post)

    send_via_mandrill(CONNECTION, "user@example.com", "Hi", "text", "<p>hi</p>")

    assert "headers" not in captured["payload"]["message"]
