#!/usr/bin/env python3
"""Network egress diagnostic — run ON THE HOSTING SERVER.

Figures out why SMTP send + AutoTrader retrieve fail after the server move.

Usage (from the app directory, with the same python the app uses):
    python diagnose_network.py

It tests, for each target:
  - DNS resolution (does the host name resolve at all?)
  - TCP connect (can the server reach the port?)
  - TLS handshake (for SMTP/HTTPS ports)
  - SMTP banner + STARTTLS/auth (for your SMTP host)

Targets: your SMTP host on 587 + 465, your SOCKS proxy on 1080, autotrader.ca on 443,
and common reference hosts (google, cloudflare). Copy the full output into your support
ticket — it shows exactly which ports are open vs refused vs timed out.
"""
import os
import socket
import ssl
import sys
import time

# ---- EDIT THESE if you want to test your real SMTP host + SOCKS -----------------
SMTP_HOST = os.environ.get("SMTP_HOST", "smtp.gmail.com")   # change to your SMTP host
SOCKS_HOST = os.environ.get("SOCKS_HOST", "169.197.93.90")
SOCKS_PORT = int(os.environ.get("SOCKS_PORT", "1080"))

TARGETS = [
    ("Google DNS 443 (baseline web)", "www.google.com", 443, False),
    ("Cloudflare 443 (baseline web)", "1.1.1.1", 443, False),
    ("AutoTrader 443 (direct)", "www.autotrader.ca", 443, False),
    (f"SOCKS proxy {SOCKS_HOST}:{SOCKS_PORT}", SOCKS_HOST, SOCKS_PORT, False),
    (f"SMTP {SMTP_HOST}:587 (STARTTLS)", SMTP_HOST, 587, False),
    (f"SMTP {SMTP_HOST}:465 (SSL)", SMTP_HOST, 465, True),
    ("Gmail SMTP 587 (baseline)", "smtp.gmail.com", 587, False),
    ("Gmail SMTP 465 (baseline)", "smtp.gmail.com", 465, True),
]


def resolve(host):
    try:
        ip = socket.gethostbyname(host)
        return ip, None
    except Exception as e:
        return None, str(e)


def tcp_connect(ip, port, timeout=10):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(timeout)
    t0 = time.time()
    try:
        s.connect((ip, port))
        return time.time() - t0, None
    except Exception as e:
        return None, f"{type(e).__name__}: {e}"
    finally:
        s.close()


def tls_handshake(ip, port, host, timeout=10):
    ctx = ssl.create_default_context()
    try:
        with socket.create_connection((ip, port), timeout=timeout) as sock:
            with ctx.wrap_socket(sock, server_hostname=host) as ssock:
                return ssock.version(), ssock.getpeercert().get("subject", "(no subject)"), None
    except Exception as e:
        return None, None, f"{type(e).__name__}: {e}"


def smtp_probe(ip, port, host, use_ssl, timeout=15):
    """Read the SMTP banner, EHLO, STARTTLS (if 587). Returns (banner, notes, err)."""
    ctx = ssl.create_default_context()
    try:
        raw = socket.create_connection((ip, port), timeout=timeout)
        if use_ssl:
            raw = ctx.wrap_socket(raw, server_hostname=host)
        raw.settimeout(timeout)
        banner = raw.recv(4096).decode("utf-8", "replace").strip()
        raw.sendall(b"EHLO diagnose.local\r\n")
        ehlo = raw.recv(4096).decode("utf-8", "replace").strip()
        notes = "EHLO OK"
        if not use_ssl and "STARTTLS" in ehlo.upper():
            raw.sendall(b"STARTTLS\r\n")
            st = raw.recv(4096).decode("utf-8", "replace").strip()
            raw = ctx.wrap_socket(raw, server_hostname=host)
            notes += " | STARTTLS OK"
        try:
            raw.sendall(b"QUIT\r\n")
        except Exception:
            pass
        raw.close()
        return banner, notes, None
    except Exception as e:
        return None, None, f"{type(e).__name__}: {e}"


def main():
    print("=" * 78)
    print("NETWORK EGRESS DIAGNOSTIC")
    print(f"Python: {sys.version.split()[0]}  |  Hostname: {socket.gethostname()}")
    print(f"SMTP_HOST={SMTP_HOST}  SOCKS={SOCKS_HOST}:{SOCKS_PORT}")
    print("Set SMTP_HOST / SOCKS_HOST / SOCKS_PORT env vars to test your real values.")
    print("=" * 78)
    for label, host, port, use_ssl in TARGETS:
        print(f"\n--- {label} ---")
        ip, err = resolve(host)
        if err:
            print(f"  DNS:   FAIL ({err})")
            continue
        print(f"  DNS:   {host} -> {ip}")
        elapsed, terr = tcp_connect(ip, port)
        if terr:
            tag = "REFUSED" if "refused" in terr.lower() else ("TIMEOUT" if "timed out" in terr.lower() else "FAIL")
            print(f"  TCP:   {tag} ({terr})")
            continue
        print(f"  TCP:   OPEN (connected in {elapsed:.2f}s)")
        if port in (443, 465) or use_ssl:
            ver, subj, terr = tls_handshake(ip, port, host)
            if terr:
                print(f"  TLS:   FAIL ({terr})")
            else:
                print(f"  TLS:   OK ({ver})  cert subject: {subj}")
        if port in (587, 465):
            banner, notes, serr = smtp_probe(ip, port, host, use_ssl)
            if serr:
                print(f"  SMTP:  FAIL ({serr})")
            else:
                print(f"  SMTP:  banner: {banner[:80] if banner else '(none)'}")
                print(f"         {notes}")
    print("\n" + "=" * 78)
    print("HOW TO READ THIS")
    print("- If 443 (web) works but 587/465/1080 REFUSE/TIMEOUT -> the host blocks those")
    print("  outbound ports. Send this output to your host; ask them to open 587, 465, 1080.")
    print("- If 587/465 are OPEN but SMTP banner FAILS -> your SMTP provider is blocking the")
    print("  server's IP (whitelist it at the mail provider).")
    print("- If SOCKS TCP REFUSED/TIMEOUT from here but works from your PC -> the SOCKS")
    print("  provider isn't reachable from this server (whitelist the server IP there).")
    print("=" * 78)


if __name__ == "__main__":
    main()
