"""Tests para smtp_send usando un mock SMTP server con smtpd/aiosmtpd o threading."""

import smtplib
import socket
import sys
import os
import threading
import time
from io import StringIO

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from infra.smtp_send import smtp_send, SMTPConfigPy, EmailAttachmentPy


def _find_free_port() -> int:
    """Encuentra un puerto libre en 127.0.0.1."""
    with socket.socket() as s:
        s.bind(("127.0.0.1", 0))
        return s.getsockname()[1]


class _MockSMTPHandler:
    """Handler de conexion SMTP minimal para tests."""

    def __init__(self, conn: socket.socket):
        self.conn = conn

    def run(self):
        f = self.conn.makefile("rb")
        try:
            self._send(b"220 mock SMTP\r\n")
            while True:
                line = f.readline()
                if not line:
                    break
                cmd = line.decode("utf-8", errors="replace").strip().upper()
                if cmd.startswith("EHLO") or cmd.startswith("HELO"):
                    self._send(b"250 mock\r\n")
                elif cmd.startswith("MAIL FROM"):
                    self._send(b"250 OK\r\n")
                elif cmd.startswith("RCPT TO"):
                    self._send(b"250 OK\r\n")
                elif cmd.startswith("DATA"):
                    self._send(b"354 End with .\r\n")
                    while True:
                        dl = f.readline()
                        if dl.strip() == b".":
                            break
                    self._send(b"250 OK\r\n")
                elif cmd.startswith("QUIT"):
                    self._send(b"221 Bye\r\n")
                    break
                else:
                    self._send(b"502 Not recognized\r\n")
        finally:
            self.conn.close()

    def _send(self, data: bytes):
        self.conn.sendall(data)


class _MockSMTPServer:
    """SMTP server minimal corriendo en thread para tests."""

    def __init__(self):
        self.port = _find_free_port()
        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self._sock.bind(("127.0.0.1", self.port))
        self._sock.listen(5)
        self._sock.settimeout(2.0)
        self._stop = threading.Event()
        self._thread = threading.Thread(target=self._serve, daemon=True)

    def start(self):
        self._thread.start()
        return self

    def stop(self):
        self._stop.set()
        self._sock.close()

    def _serve(self):
        while not self._stop.is_set():
            try:
                conn, _ = self._sock.accept()
                t = threading.Thread(target=_MockSMTPHandler(conn).run, daemon=True)
                t.start()
            except OSError:
                break

    def cfg(self) -> SMTPConfigPy:
        return SMTPConfigPy(host="127.0.0.1", port=self.port, tls_mode="")


def test_envia_texto_plano_via_mock_smtpd():
    srv = _MockSMTPServer().start()
    try:
        smtp_send(
            srv.cfg(),
            from_addr="alice@example.com",
            to=["bob@example.com"],
            subject="Test plain",
            body_text="Hello Bob",
        )
    finally:
        srv.stop()


def test_envia_html_via_mock_smtpd():
    srv = _MockSMTPServer().start()
    try:
        smtp_send(
            srv.cfg(),
            from_addr="alice@example.com",
            to=["bob@example.com"],
            subject="Test HTML",
            body_html="<b>Hello</b>",
        )
    finally:
        srv.stop()


def test_envia_con_adjunto_via_mock_smtpd():
    srv = _MockSMTPServer().start()
    try:
        att = EmailAttachmentPy(filename="f.txt", content_type="text/plain", data=b"file data")
        smtp_send(
            srv.cfg(),
            from_addr="alice@example.com",
            to=["bob@example.com"],
            subject="Test attachment",
            body_text="See attachment",
            attachments=[att],
        )
    finally:
        srv.stop()


def test_error_si_host_no_existe():
    port = _find_free_port()
    # Port is free but nothing listens — should get connection refused
    cfg = SMTPConfigPy(host="127.0.0.1", port=port, tls_mode="")
    try:
        smtp_send(cfg, "a@x.com", ["b@x.com"], "sub", body_text="hi")
        assert False, "Expected RuntimeError"
    except RuntimeError:
        pass
