"""Synthetic local tests. No SMTP server, GitHub run, or mailbox is contacted."""

import contextlib
import importlib.util
import io
import json
from pathlib import Path
import smtplib
import ssl
import unittest
from unittest.mock import MagicMock, patch

SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "send_push_email.py"
spec = importlib.util.spec_from_file_location("send_push_email", SCRIPT)
mailer = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mailer)


class PushEmailTests(unittest.TestCase):
    def setUp(self):
        self.env = {
            "SMTP_HOST": "smtp.example.com", "SMTP_PORT": "587",
            "SMTP_USERNAME": "synthetic-user", "SMTP_PASSWORD": "synthetic-secret",
            "MAIL_FROM": "sender@example.com", "MAIL_TO": "consented@example.net",
            "GITHUB_REPOSITORY": "example/notification-probe",
            "GITHUB_REF": "refs/heads/main", "GITHUB_SHA": "a" * 40,
            "GITHUB_RUN_ID": "123456789", "GITHUB_RUN_ATTEMPT": "1",
            "GITHUB_SERVER_URL": "https://github.com", "GITHUB_ACTOR": "example-user",
            "UNUSED_COMMIT_MESSAGE": "DO_NOT_INCLUDE_COMMIT_OR_CODE_CONTENT",
        }
        self.factory = MagicMock()
        self.smtp = self.factory.return_value.__enter__.return_value
        self.smtp.send_message.return_value = {}
        guard = patch("socket.create_connection", side_effect=AssertionError("network forbidden"))
        guard.start()
        self.addCleanup(guard.stop)

    def execute(self):
        stream = io.StringIO()
        with contextlib.redirect_stdout(stream):
            code = mailer.main(self.env, self.factory)
        output = stream.getvalue()
        for value in (self.env["SMTP_PASSWORD"], self.env["SMTP_USERNAME"],
                      self.env["MAIL_FROM"], self.env["MAIL_TO"]):
            self.assertNotIn(value, output)
        return code, json.loads(output)

    def test_acceptance_records_sha_run_and_verified_starttls(self):
        code, record = self.execute()
        self.assertEqual((code, record["stage"]), (0, "smtp_accepted"))
        self.assertEqual(record["sha"], "a" * 40)
        self.assertEqual(record["run_id"], "123456789")
        self.assertEqual(self.smtp.ehlo.call_count, 2)
        context = self.smtp.starttls.call_args.kwargs["context"]
        self.assertTrue(context.check_hostname)
        self.assertEqual(context.verify_mode, ssl.CERT_REQUIRED)
        self.factory.assert_called_once_with("smtp.example.com", 587, timeout=30)

    def test_message_has_one_recipient_and_metadata_not_commit_content(self):
        self.execute()
        message = self.smtp.send_message.call_args.args[0]
        self.assertEqual(message["To"], self.env["MAIL_TO"])
        self.assertEqual(self.smtp.send_message.call_args.kwargs["to_addrs"],
                         [self.env["MAIL_TO"]])
        body = message.get_content()
        self.assertIn("/actions/runs/123456789/attempts/1", body)
        self.assertIn("a" * 40, body)
        self.assertNotIn(self.env["UNUSED_COMMIT_MESSAGE"], body)
        self.assertNotIn(self.env["SMTP_PASSWORD"], body)
        self.assertIn("not deployment success", body)

    def test_authentication_failure_is_nonzero_without_exception_text(self):
        self.smtp.login.side_effect = smtplib.SMTPAuthenticationError(
            535, b"synthetic-secret sender@example.com")
        code, record = self.execute()
        self.assertEqual((code, record["stage"]), (1, "smtp_authentication_failed"))
        self.smtp.send_message.assert_not_called()

    def test_missing_secret_fails_before_connection(self):
        self.env["SMTP_PASSWORD"] = ""
        # Check empty secrets separately because the generic leak assertion needs a value.
        stream = io.StringIO()
        with contextlib.redirect_stdout(stream):
            code = mailer.main(self.env, self.factory)
        self.assertEqual(code, 2)
        self.assertEqual(json.loads(stream.getvalue())["stage"], "configuration_error")
        self.factory.assert_not_called()

    def test_tls_failure_does_not_fall_back_to_plaintext(self):
        self.smtp.starttls.side_effect = smtplib.SMTPNotSupportedError("STARTTLS unavailable")
        code, record = self.execute()
        self.assertEqual((code, record["stage"]), (1, "smtp_error"))
        self.smtp.login.assert_not_called()
        self.smtp.send_message.assert_not_called()

    def test_recipient_exception_is_not_acceptance(self):
        self.smtp.send_message.side_effect = smtplib.SMTPRecipientsRefused(
            {self.env["MAIL_TO"]: (550, b"refused")})
        code, record = self.execute()
        self.assertEqual((code, record["stage"]), (1, "smtp_recipient_refused"))

    def test_nonempty_refusal_result_is_not_acceptance(self):
        self.smtp.send_message.return_value = {self.env["MAIL_TO"]: (550, b"refused")}
        code, record = self.execute()
        self.assertEqual((code, record["stage"]), (1, "smtp_recipient_refused"))

    def test_timeout_is_nonzero_and_not_automatically_retried(self):
        self.smtp.send_message.side_effect = TimeoutError("synthetic timeout")
        code, record = self.execute()
        self.assertEqual((code, record["stage"]), (1, "smtp_error"))
        self.smtp.send_message.assert_called_once()

    def test_header_injection_fails_before_connection(self):
        self.env["MAIL_TO"] = "consented@example.net\nBcc: unwanted@example.org"
        code, record = self.execute()
        self.assertEqual((code, record["stage"]), (2, "configuration_error"))
        self.factory.assert_not_called()

    def test_recipient_list_is_rejected_before_connection(self):
        self.env["MAIL_TO"] = "one@example.net,two@example.net"
        code, record = self.execute()
        self.assertEqual((code, record["stage"]), (2, "configuration_error"))
        self.factory.assert_not_called()


if __name__ == "__main__":
    unittest.main(verbosity=2)
