#!/usr/bin/env python3
"""Tests for gmail_send (DWD Gmail-API send) + digest_mailer (transport routing).

No network: the credential provider and urllib.request.urlopen are monkeypatched.
Run directly or via backend/run_tests.py.
"""
import base64
import io
import json
import os
import sys
import urllib.error
import urllib.request

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from email_integration import gmail_send
import digest_mailer
import smtp_send

FAILS = []


def check(cond, msg):
    print(("  ok  " if cond else "  XX  ") + msg)
    if not cond:
        FAILS.append(msg)


class _Tok:
    def __init__(self, t):
        self.token = t


class _Provider:
    def __init__(self):
        self.calls = []

    def access_token_for(self, email, scope):
        self.calls.append((email, scope))
        return _Tok("tok-" + email)


class _Resp:
    def __init__(self, payload):
        self._p = payload

    def read(self):
        return json.dumps(self._p).encode()

    def __enter__(self):
        return self

    def __exit__(self, *a):
        return False


def main():
    # 1. _build_raw round-trips
    raw = gmail_send._build_raw("a@x", ["b@x", "c@x"], "Subj", "Hello body")
    decoded = base64.urlsafe_b64decode(raw).decode()
    check("From: a@x" in decoded and "Subject: Subj" in decoded
          and "b@x, c@x" in decoded and "Hello body" in decoded,
          "_build_raw round-trips From/To/Subject/body")

    # 2. send_via_gmail posts correctly (fake provider + fake urlopen)
    captured = {}
    orig_open = urllib.request.urlopen
    orig_build = gmail_send._creds.build_provider
    prov = _Provider()
    gmail_send._creds.build_provider = lambda factory: prov

    def fake_open(req, timeout=None):
        captured["url"] = req.full_url
        captured["auth"] = req.headers.get("Authorization")
        captured["body"] = json.loads(req.data.decode())
        return _Resp({"id": "msg123", "threadId": "thr123"})

    urllib.request.urlopen = fake_open
    try:
        res = gmail_send.send_via_gmail("grant@ten31.xyz",
                                        ["a@ten31.xyz", "b@ten31.xyz"], "S", "B")
    finally:
        urllib.request.urlopen = orig_open
        gmail_send._creds.build_provider = orig_build

    check(res["message_id"] == "msg123", "send returns message_id")
    check(res["from"] == "grant@ten31.xyz", "send reports from")
    check(res["sent_to"] == ["a@ten31.xyz", "b@ten31.xyz"], "send reports recipients")
    check(captured["url"].endswith("/users/grant%40ten31.xyz/messages/send"),
          "posts to messages/send for the impersonated user")
    check(captured["auth"] == "Bearer tok-grant@ten31.xyz", "uses the compose-scoped token")
    check("raw" in captured["body"] and "message" not in captured["body"],
          "send body is {raw:...} (not nested under message, unlike drafts)")
    check(prov.calls and prov.calls[0][1] == gmail_send._creds.GMAIL_COMPOSE_SCOPE,
          "requests the gmail.compose scope")

    # 3. validation
    try:
        gmail_send.send_via_gmail("x@x", [], "s", "b"); ok = False
    except ValueError:
        ok = True
    check(ok, "empty recipients -> ValueError")
    try:
        gmail_send.send_via_gmail("", ["a@x"], "s", "b"); ok = False
    except ValueError:
        ok = True
    check(ok, "missing sender -> ValueError")

    # 4. transport() selection
    orig_avail = gmail_send.gmail_available
    orig_smtp_cfg = smtp_send.smtp_configured
    try:
        gmail_send.gmail_available = lambda: True
        check(digest_mailer.transport() == "gmail-dwd", "transport prefers gmail-dwd")
        gmail_send.gmail_available = lambda: False
        smtp_send.smtp_configured = lambda: True
        check(digest_mailer.transport() == "smtp", "transport falls back to smtp")
        smtp_send.smtp_configured = lambda: False
        check(digest_mailer.transport() is None, "transport None when neither configured")
    finally:
        gmail_send.gmail_available = orig_avail
        smtp_send.smtp_configured = orig_smtp_cfg

    # 5. send_digest routes to gmail + honors CRM_DIGEST_SENDER
    orig_send = gmail_send.send_via_gmail
    sent = {}
    try:
        gmail_send.gmail_available = lambda: True

        def fake_send(sender, to, subj, body, conn=None):
            sent.update(sender=sender, to=to)
            return {"sent_to": to, "from": sender, "message_id": "m"}

        gmail_send.send_via_gmail = fake_send
        os.environ["CRM_DIGEST_SENDER"] = "digest@ten31.xyz"
        res = digest_mailer.send_digest(None, ["a@ten31.xyz"], "S", "B")
        check(res["transport"] == "gmail-dwd", "send_digest tags transport gmail-dwd")
        check(sent["sender"] == "digest@ten31.xyz", "send_digest uses CRM_DIGEST_SENDER")
    finally:
        gmail_send.send_via_gmail = orig_send
        gmail_send.gmail_available = orig_avail
        os.environ.pop("CRM_DIGEST_SENDER", None)

    # 6. send_digest raises NoTransport when neither is available
    try:
        gmail_send.gmail_available = lambda: False
        smtp_send.smtp_configured = lambda: False
        try:
            digest_mailer.send_digest(None, ["a@x"], "S", "B"); ok = False
        except digest_mailer.NoTransport:
            ok = True
        check(ok, "send_digest raises NoTransport when no transport")
    finally:
        gmail_send.gmail_available = orig_avail
        smtp_send.smtp_configured = orig_smtp_cfg

    # 7. HTTPError from Gmail is wrapped as RuntimeError (not raised raw)
    orig_build = gmail_send._creds.build_provider
    gmail_send._creds.build_provider = lambda factory: _Provider()
    orig_open2 = urllib.request.urlopen

    def raise_http(req, timeout=None):
        raise urllib.error.HTTPError(req.full_url, 403, "Forbidden", {},
                                     io.BytesIO(b'{"error":"denied"}'))

    urllib.request.urlopen = raise_http
    try:
        try:
            gmail_send.send_via_gmail("g@x", ["a@x"], "s", "b"); ok = False
        except RuntimeError as e:
            ok = "403" in str(e)
        except urllib.error.HTTPError:
            ok = False  # should have been wrapped, not raised raw
    finally:
        urllib.request.urlopen = orig_open2
        gmail_send._creds.build_provider = orig_build
    check(ok, "Gmail HTTPError wrapped as RuntimeError carrying the status")

    # 8. default_sender: env wins; else DB first-admin; else None
    class _FakeCur:
        def __init__(self, row):
            self._row = row

        def fetchone(self):
            return self._row

    class _Conn:
        def __init__(self, row):
            self._row = row

        def execute(self, *a, **k):
            return _FakeCur(self._row)

    os.environ.pop("CRM_DIGEST_SENDER", None)
    check(digest_mailer.default_sender(_Conn({"email": "first@ten31.xyz"})) == "first@ten31.xyz",
          "default_sender falls back to first active admin")
    check(digest_mailer.default_sender(_Conn(None)) is None,
          "default_sender returns None when no admin has an email")
    os.environ["CRM_DIGEST_SENDER"] = "env@ten31.xyz"
    check(digest_mailer.default_sender(_Conn({"email": "first@ten31.xyz"})) == "env@ten31.xyz",
          "CRM_DIGEST_SENDER overrides the DB lookup")
    os.environ.pop("CRM_DIGEST_SENDER", None)

    # 9. send_digest tags transport 'smtp' on the fallback path
    orig_avail2 = gmail_send.gmail_available
    orig_smtp_cfg2 = smtp_send.smtp_configured
    orig_smtp_send = smtp_send.send_email
    try:
        gmail_send.gmail_available = lambda: False
        smtp_send.smtp_configured = lambda: True
        smtp_send.send_email = lambda to, subj, body, **k: {"sent_to": to, "from": "f@x"}
        res = digest_mailer.send_digest(None, ["a@x"], "S", "B")
        check(res["transport"] == "smtp", "send_digest tags transport smtp on fallback")
    finally:
        gmail_send.gmail_available = orig_avail2
        smtp_send.smtp_configured = orig_smtp_cfg2
        smtp_send.send_email = orig_smtp_send

    print()
    if FAILS:
        print(f"FAILED ({len(FAILS)}):")
        for f in FAILS:
            print("  -", f)
        raise SystemExit(1)
    print("ALL PASS (gmail_send + digest_mailer)")


if __name__ == "__main__":
    main()
