#!/usr/bin/env python3
"""
██████╗       ██████╗      ███╗   ███╗ █████╗ ██╗██╗     ███████╗██████╗
██╔══██╗      ██╔══██╗     ████╗ ████║██╔══██╗██║██║     ██╔════╝██╔══██╗
██║  ██║█████╗██║  ██║     ██╔████╔██║███████║██║██║     █████╗  ██████╔╝
██║  ██║╚════╝██║  ██║     ██║╚██╔╝██║██╔══██║██║██║     ██╔══╝  ██╔══██╗
██████╔╝      ██████╔╝     ██║ ╚═╝ ██║██║  ██║██║███████╗███████╗██║  ██║
╚═════╝       ╚═════╝      ╚═╝     ╚═╝╚═╝  ╚═╝╚═╝╚══════╝╚══════╝╚═╝  ╚═╝
 by LA CASA DEL TRUCO  ·  Darkbyte & Dirty  ·  2026
"""

import tkinter as tk
from tkinter import filedialog, messagebox
import customtkinter as ctk
from PIL import Image
import smtplib
import ssl
import threading
import time
import random
import re
import os
import csv
import queue
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email.utils import formatdate, make_msgid, formataddr
from email import encoders
from datetime import datetime
import html as html_lib

# Import our custom license manager
try:
    import license_manager
except ImportError:
    license_manager = None

# ─── Appearance ──────────────────────────────────────────────────────────────
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("dark-blue")

# ─── Pixel-art colour palette ────────────────────────────────────────────────
C = {
    # backgrounds
    "bg":        "#0D0D0D",   # pure black
    "surface":   "#1A1A1A",   # very dark gray
    "card":      "#242424",   # dark gray
    "input_bg":  "#101010",   # near-black for inputs

    # accents
    "red":       "#CC2222",   # primary accent
    "red_dark":  "#881111",   # hover / darker
    "red_dim":   "#441111",   # subtle red tint

    # neutrals
    "white":     "#F2F0EC",   # off-white / text
    "gray":      "#888880",   # muted text
    "gray_dark": "#444440",   # borders
    "gray_light":"#BBBBBB",   # secondary labels

    # brand
    "brown":     "#C4A882",   # light brown – brand accent
    "brown_dk":  "#8B6E4E",   # darker brown
    "brown_dim": "#3D2B1F",   # very dark brown

    # status
    "ok":        "#888880",   # success → gray (palette constraint)
    "warn":      "#C09040",   # warning → brownish
    "err":       "#CC2222",   # error   → red
}

# Golden ratio window: 987 × 610  (φ ≈ 1.618)
WIN_W, WIN_H = 987, 610

# ─── Pixel font wrapper ───────────────────────────────────────────────────────
def PF(size=12, weight="normal"):
    """Courier-based pixel-art font."""
    return ctk.CTkFont(family="Courier New", size=size, weight=weight)

def SF(size=12, weight="normal"):
    """System font for labels that need clarity."""
    return ctk.CTkFont(family="Courier New", size=size, weight=weight)

# ─── Helpers ──────────────────────────────────────────────────────────────────
EMAIL_RE = re.compile(r"[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}")

def extract_emails(raw: str) -> list:
    found = EMAIL_RE.findall(raw)
    seen, result = set(), []
    for e in found:
        el = e.lower()
        if el not in seen:
            seen.add(el)
            result.append(e)
    return result

def ts():
    return datetime.now().strftime("%H:%M:%S")

# ─── Pixel-art decorators (ASCII) ─────────────────────────────────────────────
LOGO = (
    " ██████╗ ██████╗     ███╗   ███╗ █████╗ ██╗██╗     ███████╗██████╗ \n"
    " ██╔══██╗╚════██╗    ████╗ ████║██╔══██╗██║██║     ██╔════╝██╔══██╗\n"
    " ██║  ██║ █████╔╝    ██╔████╔██║███████║██║██║     █████╗  ██████╔╝\n"
    " ██║  ██║██╔═══╝     ██║╚██╔╝██║██╔══██║██║██║     ██╔══╝  ██╔══██╗\n"
    " ██████╔╝███████╗    ██║ ╚═╝ ██║██║  ██║██║███████╗███████╗██║  ██║\n"
    " ╚═════╝ ╚══════╝    ╚═╝     ╚═╝╚═╝  ╚═╝╚═╝╚══════╝╚══════╝╚═╝  ╚═╝"
)


# ═══════════════════════════════════════════════════════════════════════════════
class MassMailerApp(ctk.CTk):

    def __init__(self):
        super().__init__()
        self.title("D-D Mailer  ·  La Casa del Truco")
        self.geometry(f"{WIN_W}x{WIN_H}")
        self.resizable(False, False)
        self.configure(fg_color=C["bg"])

        # Check license
        self.is_licensed = False
        if license_manager:
            self.is_licensed = license_manager.is_activated()
        else:
            self.is_licensed = True

        if not self.is_licensed:
            self._build_activation_ui()
        else:
            self._build_app_ui()

    def _build_activation_ui(self):
        # 1. Header
        hdr = ctk.CTkFrame(self, fg_color=C["surface"], corner_radius=0, height=64)
        hdr.pack(side="top", fill="x")
        hdr.pack_propagate(False)

        ctk.CTkFrame(hdr, fg_color=C["red"], corner_radius=0, width=6).pack(side="left", fill="y")
        
        txt_box = ctk.CTkFrame(hdr, fg_color="transparent", corner_radius=0)
        txt_box.pack(side="left", padx=(10, 0))

        ctk.CTkLabel(txt_box, text="D-D MAILER", font=PF(20, "bold"), text_color=C["red"], anchor="w").pack(anchor="w")
        ctk.CTkLabel(txt_box, text="[ CORE LOCKED // ACTIVATION ENFORCEMENT ]", font=PF(9), text_color=C["gray"], anchor="w").pack(anchor="w")

        # pixel divider
        ctk.CTkFrame(self, fg_color=C["red"], corner_radius=0, height=2).pack(fill="x")

        # Main lock panel
        lock_panel = ctk.CTkFrame(self, fg_color=C["surface"], corner_radius=0)
        lock_panel.pack(fill="both", expand=True, padx=40, pady=40)

        ctk.CTkLabel(lock_panel, text="🔑", font=PF(36)).pack(pady=(20, 10))
        ctk.CTkLabel(lock_panel, text="SYSTEM HARDWARE ACTIVATION REQUIRED", font=PF(14, "bold"), text_color=C["red"]).pack(pady=(0, 20))

        # HWID section
        ctk.CTkLabel(lock_panel, text="▌ YOUR SYSTEM MACHINE ID (HWID):", font=PF(11, "bold"), text_color=C["brown"]).pack(anchor="w", padx=40)
        
        hwid = license_manager.get_machine_id() if license_manager else "UNKNOWN_HWID"

        hwid_entry = ctk.CTkEntry(lock_panel, fg_color=C["input_bg"], border_color=C["gray_dark"],
                                  border_width=1, text_color=C["white"], font=PF(11), corner_radius=0, height=34)
        hwid_entry.insert(0, hwid)
        hwid_entry.configure(state="readonly")
        hwid_entry.pack(fill="x", padx=40, pady=(4, 10))

        def copy_hwid():
            self.clipboard_clear()
            self.clipboard_append(hwid)
            messagebox.showinfo("Clipboard", "Machine ID copied to clipboard.")

        ctk.CTkButton(lock_panel, text="[ COPY MACHINE ID ]", font=PF(11, "bold"),
                       fg_color=C["card"], hover_color=C["gray_dark"],
                       text_color=C["gray_light"], corner_radius=0, height=32,
                       border_width=1, border_color=C["gray_dark"], command=copy_hwid).pack(pady=(0, 25))

        # License key section
        ctk.CTkLabel(lock_panel, text="▌ ENTER ACTIVATION KEY:", font=PF(11, "bold"), text_color=C["brown"]).pack(anchor="w", padx=40)
        
        license_entry = ctk.CTkEntry(lock_panel, fg_color=C["input_bg"], border_color=C["gray_dark"],
                                     border_width=1, text_color=C["white"], font=PF(11), corner_radius=0, height=34)
        license_entry.pack(fill="x", padx=40, pady=(4, 20))

        def attempt_activation():
            key = license_entry.get().strip()
            if not key:
                messagebox.showerror("Activation Failed", "Please enter your license key.")
                return
            
            if license_manager and license_manager.save_license(key):
                messagebox.showinfo("Success", "System activated successfully. Modules unlocked.")
                for widget in self.winfo_children():
                    widget.destroy()
                self._build_app_ui()
            else:
                messagebox.showerror("Activation Failed", "Invalid Activation Key for this Machine ID.")

        ctk.CTkButton(lock_panel, text="[ INJECT SYNERGY CODE ]", font=PF(12, "bold"),
                       fg_color=C["red"], hover_color=C["red_dark"],
                       text_color=C["white"], corner_radius=0, height=40,
                       command=attempt_activation).pack(fill="x", padx=40, pady=(10, 0))

        # footer
        div = ctk.CTkFrame(self, fg_color=C["red"], corner_radius=0, height=2)
        div.pack(fill="x", side="bottom")

        panel = ctk.CTkFrame(self, fg_color=C["surface"], corner_radius=0, height=38)
        panel.pack(fill="x", side="bottom")
        panel.pack_propagate(False)
        ctk.CTkLabel(panel, text=" LA CASA DEL TRUCO  //  SECURITY GATE v2.0", font=PF(8), text_color=C["gray"]).pack(pady=8)

    def _build_app_ui(self):
        # state
        self._send_thread = None
        self._pause_event = threading.Event()
        self._stop_event  = threading.Event()
        self._pause_event.set()
        self._log_queue   = queue.Queue()

        self.attachment_path = tk.StringVar(value="")
        self.total_recipients = 0
        self.sent_count = 0
        self.ok_count   = 0
        self.err_count  = 0

        # ── Load brand logo ──
        self._logo_img  = None   # full header version
        self._logo_mini = None   # stamp version
        _logo_path = os.path.join(os.path.dirname(__file__), "logo1.png")
        if os.path.isfile(_logo_path):
            try:
                raw = Image.open(_logo_path).convert("RGBA")
                # header: height 46 px, keep aspect ratio
                ratio = raw.width / raw.height
                h46  = int(46 * ratio)
                self._logo_img = ctk.CTkImage(
                    light_image=raw, dark_image=raw, size=(h46, 46))
                # stamp: height 28 px
                h28 = int(28 * ratio)
                self._logo_mini = ctk.CTkImage(
                    light_image=raw, dark_image=raw, size=(h28, 28))
            except Exception:
                pass

        self._build_ui()
        self._poll_log()

    # ── Layout ────────────────────────────────────────────────────────────────

    def _build_ui(self):
        self._build_header()
        self._build_body()
        self._build_footer()

    # ── Header  (pixelart brand bar) ──────────────────────────────────────────
    def _build_header(self):
        hdr = ctk.CTkFrame(self, fg_color=C["surface"], corner_radius=0, height=64)
        hdr.pack(side="top", fill="x")
        hdr.pack_propagate(False)

        # left: red stripe
        ctk.CTkFrame(hdr, fg_color=C["red"], corner_radius=0, width=6).pack(
            side="left", fill="y")

        # logo image (if loaded)
        if self._logo_img:
            ctk.CTkLabel(
                hdr, image=self._logo_img, text="",
                fg_color="transparent",
            ).pack(side="left", padx=(10, 0))

        # brand text stack
        txt_box = ctk.CTkFrame(hdr, fg_color="transparent", corner_radius=0)
        txt_box.pack(side="left", padx=(10, 0))

        ctk.CTkLabel(
            txt_box,
            text="D-D MAILER",
            font=PF(20, "bold"),
            text_color=C["red"],
            anchor="w",
        ).pack(anchor="w")
        ctk.CTkLabel(
            txt_box,
            text="[ DARKBYTE & DIRTY  //  BULK SMTP ENGINE ]",
            font=PF(9),
            text_color=C["gray"],
            anchor="w",
        ).pack(anchor="w")

        # right: credits
        credit_box = ctk.CTkFrame(hdr, fg_color="transparent", corner_radius=0)
        credit_box.pack(side="right", padx=14)

        ctk.CTkLabel(
            credit_box,
            text="LA CASA DEL TRUCO",
            font=PF(9, "bold"),
            text_color=C["brown"],
        ).pack(anchor="e")
        ctk.CTkLabel(
            credit_box,
            text="Darkbyte  &  Dirty  //  2026",
            font=PF(8),
            text_color=C["gray"],
        ).pack(anchor="e")

        # pixel divider
        div = ctk.CTkFrame(self, fg_color=C["red"], corner_radius=0, height=2)
        div.pack(fill="x")

    # ── Body (tab container + right panel) ────────────────────────────────────
    def _build_body(self):
        body = ctk.CTkFrame(self, fg_color="transparent", corner_radius=0)
        body.pack(fill="both", expand=True, side="top")

        # ── Tabview (left) ──
        self.tabview = ctk.CTkTabview(
            body,
            fg_color=C["surface"],
            segmented_button_fg_color=C["card"],
            segmented_button_selected_color=C["red"],
            segmented_button_selected_hover_color=C["red_dark"],
            segmented_button_unselected_color=C["card"],
            segmented_button_unselected_hover_color=C["gray_dark"],
            text_color=C["white"],
            corner_radius=0,
        )
        self.tabview.pack(side="left", fill="both", expand=True, padx=(0, 0))

        self.tabview.add("[ SMTP ]")
        self.tabview.add("[ RECIPIENTS ]")
        self.tabview.add("[ MESSAGE ]")

        self._build_smtp_tab(self.tabview.tab("[ SMTP ]"))
        self._build_recipients_tab(self.tabview.tab("[ RECIPIENTS ]"))
        self._build_message_tab(self.tabview.tab("[ MESSAGE ]"))

        # vertical pixel divider
        vdiv = ctk.CTkFrame(body, fg_color=C["red"], corner_radius=0, width=2)
        vdiv.pack(side="left", fill="y")

        # ── Right panel ──
        right = ctk.CTkFrame(body, fg_color=C["surface"], corner_radius=0, width=235)
        right.pack(side="right", fill="y")
        right.pack_propagate(False)
        self._build_right(right)

    # ── Footer  (log panel) ───────────────────────────────────────────────────
    def _build_footer(self):
        div = ctk.CTkFrame(self, fg_color=C["red"], corner_radius=0, height=2)
        div.pack(fill="x", side="bottom")

        panel = ctk.CTkFrame(self, fg_color=C["surface"], corner_radius=0, height=148)
        panel.pack(fill="x", side="bottom")
        panel.pack_propagate(False)

        top = ctk.CTkFrame(panel, fg_color="transparent", corner_radius=0)
        top.pack(fill="x", padx=8, pady=(4, 2))

        ctk.CTkLabel(
            top, text="▌ ACTIVITY LOG",
            font=PF(10, "bold"), text_color=C["brown"]
        ).pack(side="left")

        ctk.CTkButton(
            top, text="[CLR]", width=44, height=20,
            fg_color=C["card"], hover_color=C["gray_dark"],
            text_color=C["gray"], corner_radius=0, font=PF(9),
            command=self._clear_log,
        ).pack(side="right")

        self.log_box = ctk.CTkTextbox(
            panel,
            fg_color=C["input_bg"],
            text_color=C["gray_light"],
            font=PF(10),
            border_color=C["gray_dark"],
            border_width=1,
            corner_radius=0,
        )
        self.log_box.pack(fill="both", expand=True, padx=8, pady=(0, 6))
        self.log_box.configure(state="disabled")

    # ── Tab 1: SMTP ───────────────────────────────────────────────────────────
    def _build_smtp_tab(self, parent):
        parent.configure(fg_color="transparent")
        f = ctk.CTkFrame(parent, fg_color="transparent", corner_radius=0)
        f.pack(fill="both", expand=True, padx=12, pady=8)
        f.columnconfigure(1, weight=1)

        def lbl(text, row):
            ctk.CTkLabel(
                f, text=text, font=PF(11), text_color=C["gray"], anchor="w"
            ).grid(row=row, column=0, sticky="w", padx=4, pady=(8, 2))

        def entry(row, placeholder, show=None):
            kw = dict(
                master=f, placeholder_text=placeholder,
                fg_color=C["input_bg"], border_color=C["gray_dark"],
                border_width=1, text_color=C["white"],
                font=PF(11), corner_radius=0, height=34,
            )
            if show:
                kw["show"] = show
            w = ctk.CTkEntry(**kw)
            w.grid(row=row, column=1, sticky="ew", padx=4, pady=(8, 2))
            return w

        lbl("SMTP SERVER", 0);  self.smtp_host    = entry(0, "smtp.gmail.com")
        lbl("PORT",        1)
        self.smtp_port = ctk.CTkComboBox(
            f, values=["587", "465", "25"],
            fg_color=C["input_bg"], border_color=C["gray_dark"],
            button_color=C["red"], text_color=C["white"],
            dropdown_fg_color=C["card"], font=PF(11),
            corner_radius=0, height=34,
        )
        self.smtp_port.set("587")
        self.smtp_port.grid(row=1, column=1, sticky="ew", padx=4, pady=(8, 2))

        lbl("ENCRYPTION", 2)
        self.encryption = ctk.CTkComboBox(
            f, values=["TLS (STARTTLS)", "SSL/TLS", "None"],
            fg_color=C["input_bg"], border_color=C["gray_dark"],
            button_color=C["red"], text_color=C["white"],
            dropdown_fg_color=C["card"], font=PF(11),
            corner_radius=0, height=34,
        )
        self.encryption.set("TLS (STARTTLS)")
        self.encryption.grid(row=2, column=1, sticky="ew", padx=4, pady=(8, 2))

        lbl("USERNAME",   3);  self.smtp_user    = entry(3, "user@domain.com")
        lbl("PASSWORD",   4);  self.smtp_pass    = entry(4, "••••••••", show="•")
        lbl("TIMEOUT (s)",5)
        self.smtp_timeout = entry(5, "10")
        self.smtp_timeout.insert(0, "10")

        # test button
        ctk.CTkButton(
            f, text="[ TEST CONNECTION ]",
            fg_color=C["brown_dim"], hover_color=C["brown_dk"],
            text_color=C["brown"], font=PF(12, "bold"),
            corner_radius=0, height=36, border_width=1,
            border_color=C["brown_dk"],
            command=self._test_connection,
        ).grid(row=6, column=0, columnspan=2, sticky="ew", padx=4, pady=16)

        self.conn_status = ctk.CTkLabel(
            f, text="", font=PF(11), text_color=C["gray"]
        )
        self.conn_status.grid(row=7, column=0, columnspan=2, pady=2)

    # ── Tab 2: Recipients ─────────────────────────────────────────────────────
    def _build_recipients_tab(self, parent):
        parent.configure(fg_color="transparent")

        top = ctk.CTkFrame(parent, fg_color="transparent", corner_radius=0)
        top.pack(fill="x", padx=10, pady=(8, 2))

        ctk.CTkLabel(
            top, text="▌ RECIPIENT LIST",
            font=PF(11, "bold"), text_color=C["brown"]
        ).pack(side="left")

        self.recipient_counter = ctk.CTkLabel(
            top, text="[ 0 ]",
            font=PF(11, "bold"), text_color=C["red"],
        )
        self.recipient_counter.pack(side="right")

        self.recipients_text = ctk.CTkTextbox(
            parent,
            fg_color=C["input_bg"], text_color=C["white"],
            font=PF(11), border_color=C["gray_dark"],
            border_width=1, corner_radius=0,
        )
        self.recipients_text.pack(fill="both", expand=True, padx=10, pady=4)
        self.recipients_text.insert("1.0",
            "# Paste emails here — one per line\n"
            "# Lines starting with # are ignored\n\n"
        )
        self.recipients_text.bind("<KeyRelease>", self._update_recipient_count)

        btns = ctk.CTkFrame(parent, fg_color="transparent", corner_radius=0)
        btns.pack(fill="x", padx=10, pady=6)

        ctk.CTkButton(
            btns, text="[ IMPORT .TXT / .CSV ]",
            fg_color=C["card"], hover_color=C["gray_dark"],
            text_color=C["gray_light"], font=PF(11),
            corner_radius=0, height=32, border_width=1,
            border_color=C["gray_dark"],
            command=self._import_recipients,
        ).pack(side="left", padx=(0, 6))

        ctk.CTkButton(
            btns, text="[ CLR ]",
            fg_color=C["red_dim"], hover_color=C["red_dark"],
            text_color=C["red"], font=PF(11),
            corner_radius=0, height=32, width=72,
            border_width=1, border_color=C["red_dark"],
            command=self._clear_recipients,
        ).pack(side="left")

    # ── Tab 3: Message ────────────────────────────────────────────────────────
    def _build_message_tab(self, parent):
        parent.configure(fg_color="transparent")
        f = ctk.CTkFrame(parent, fg_color="transparent", corner_radius=0)
        f.pack(fill="both", expand=True, padx=10, pady=8)
        f.columnconfigure(0, weight=1)

        def row_label(text, row):
            ctk.CTkLabel(
                f, text=text, font=PF(10), text_color=C["gray"], anchor="w"
            ).grid(row=row, column=0, sticky="w", pady=(0, 2))

        def entry_field(row):
            w = ctk.CTkEntry(
                f, fg_color=C["input_bg"], border_color=C["gray_dark"],
                border_width=1, text_color=C["white"], font=PF(11),
                corner_radius=0, height=32,
            )
            w.grid(row=row, column=0, sticky="ew", pady=(0, 8))
            return w

        row_label("FROM NAME", 0);  self.from_name = entry_field(1)
        row_label("SUBJECT",   2);  self.subject   = entry_field(3)

        # body header row
        bh = ctk.CTkFrame(f, fg_color="transparent", corner_radius=0)
        bh.grid(row=4, column=0, sticky="ew")
        ctk.CTkLabel(bh, text="MESSAGE BODY", font=PF(10),
                     text_color=C["gray"]).pack(side="left")

        self.html_var = tk.BooleanVar(value=True)
        ctk.CTkSwitch(
            bh, text="HTML",
            variable=self.html_var,
            button_color=C["red"],
            progress_color=C["red_dark"],
            text_color=C["gray_light"],
            font=PF(10),
        ).pack(side="right")

        self.body_text = ctk.CTkTextbox(
            f, fg_color=C["input_bg"], text_color=C["white"],
            font=PF(10), border_color=C["gray_dark"],
            border_width=1, corner_radius=0,
        )
        self.body_text.grid(row=5, column=0, sticky="nsew", pady=(4, 8))
        f.rowconfigure(5, weight=1)

        self.body_text.insert("1.0", (
            "<html>\n<body style=\"font-family:Arial,sans-serif;"
            "color:#333;max-width:600px;margin:auto;\">\n"
            "  <h2>Hello,</h2>\n"
            "  <p>This message was sent via <strong>D-D Mailer</strong> "
            "— <em>La Casa del Truco</em></p>\n"
            "  <p>Edit this template with your content.</p>\n"
            "  <br><p style=\"color:#888;font-size:11px;\">"
            "© 2026 La Casa del Truco. All rights reserved.</p>\n"
            "</body>\n</html>"
        ))

        att = ctk.CTkFrame(f, fg_color="transparent", corner_radius=0)
        att.grid(row=6, column=0, sticky="ew")

        ctk.CTkButton(
            att, text="[ ATTACH FILE ]",
            fg_color=C["card"], hover_color=C["gray_dark"],
            text_color=C["gray_light"], font=PF(10),
            corner_radius=0, height=28, width=130,
            border_width=1, border_color=C["gray_dark"],
            command=self._pick_attachment,
        ).pack(side="left", padx=(0, 8))

        self.att_label = ctk.CTkLabel(
            att, text="-- no file --",
            font=PF(10), text_color=C["gray"]
        )
        self.att_label.pack(side="left")

        ctk.CTkButton(
            att, text="[X]", width=28, height=28,
            fg_color="transparent", hover_color=C["red_dim"],
            text_color=C["gray"], font=PF(10), corner_radius=0,
            command=self._remove_attachment,
        ).pack(side="right")

    # ── Right panel ───────────────────────────────────────────────────────────
    def _build_right(self, parent):
        def section(title, color=C["brown"]):
            f = ctk.CTkFrame(parent, fg_color=C["card"], corner_radius=0)
            f.pack(fill="x", padx=0, pady=(0, 2))
            hdr = ctk.CTkFrame(f, fg_color=C["red_dim"], corner_radius=0, height=24)
            hdr.pack(fill="x")
            hdr.pack_propagate(False)
            ctk.CTkLabel(
                hdr, text=f" ▌ {title}",
                font=PF(9, "bold"), text_color=color
            ).pack(side="left", padx=4)
            inner = ctk.CTkFrame(f, fg_color="transparent", corner_radius=0)
            inner.pack(fill="x", padx=8, pady=6)
            return inner

        # ── SEND CONTROLS ──
        sc = section("SEND CONTROLS", C["red"])

        self.btn_send = ctk.CTkButton(
            sc, text="[ ▶  SEND NOW ]",
            fg_color=C["red"], hover_color=C["red_dark"],
            text_color=C["white"], font=PF(11, "bold"),
            corner_radius=0, height=36,
            command=self._start_sending,
        )
        self.btn_send.pack(fill="x", pady=(0, 4))

        self.btn_pause = ctk.CTkButton(
            sc, text="[ ⏸  PAUSE ]",
            fg_color=C["card"], hover_color=C["gray_dark"],
            text_color=C["gray_light"], font=PF(11, "bold"),
            corner_radius=0, height=32, state="disabled",
            border_width=1, border_color=C["gray_dark"],
            command=self._toggle_pause,
        )
        self.btn_pause.pack(fill="x", pady=(0, 4))

        self.btn_stop = ctk.CTkButton(
            sc, text="[ ⏹  STOP ]",
            fg_color=C["red_dim"], hover_color=C["red_dark"],
            text_color=C["red"], font=PF(11, "bold"),
            corner_radius=0, height=32, state="disabled",
            border_width=1, border_color=C["red_dark"],
            command=self._stop_sending,
        )
        self.btn_stop.pack(fill="x")

        # ── ADVANCED ──
        adv = section("ADVANCED", C["brown"])

        def adv_row(lbl_text, var_default):
            row = ctk.CTkFrame(adv, fg_color="transparent", corner_radius=0)
            row.pack(fill="x", pady=1)
            ctk.CTkLabel(
                row, text=lbl_text, font=PF(9),
                text_color=C["gray"], anchor="w", width=108
            ).pack(side="left")
            e = ctk.CTkEntry(
                row, width=54, height=24,
                fg_color=C["input_bg"], border_color=C["gray_dark"],
                border_width=1, text_color=C["white"],
                font=PF(10), corner_radius=0,
            )
            e.insert(0, var_default)
            e.pack(side="right")
            return e

        self.conn_limit   = adv_row("CONN LIMIT",  "1")
        self.pause_every  = adv_row("PAUSE EVERY", "10")
        self.delay_min    = adv_row("DELAY MIN(s)","1")
        self.delay_max    = adv_row("DELAY MAX(s)","3")
        self.retry_limit  = adv_row("RETRIES",     "3")

        pri_row = ctk.CTkFrame(adv, fg_color="transparent", corner_radius=0)
        pri_row.pack(fill="x", pady=1)
        ctk.CTkLabel(pri_row, text="PRIORITY", font=PF(9),
                     text_color=C["gray"], anchor="w", width=108).pack(side="left")
        self.priority = ctk.CTkComboBox(
            pri_row, values=["Normal", "High"], width=72,
            fg_color=C["input_bg"], border_color=C["gray_dark"],
            button_color=C["red"], text_color=C["white"],
            dropdown_fg_color=C["card"], font=PF(9),
            corner_radius=0, height=24,
        )
        self.priority.set("Normal")
        self.priority.pack(side="right")

        # ── STATS ──
        st = section("STATISTICS", C["brown"])

        def stat_row(label, color):
            row = ctk.CTkFrame(st, fg_color="transparent", corner_radius=0)
            row.pack(fill="x", pady=1)
            ctk.CTkLabel(row, text=label, font=PF(9),
                         text_color=C["gray"], anchor="w").pack(side="left")
            lbl = ctk.CTkLabel(row, text="0", font=PF(11, "bold"), text_color=color)
            lbl.pack(side="right")
            return lbl

        self.lbl_total = stat_row("TOTAL",   C["white"])
        self.lbl_sent  = stat_row("SENT",    C["brown"])
        self.lbl_ok    = stat_row("OK",      C["ok"])
        self.lbl_err   = stat_row("ERRORS",  C["err"])

        self.progress = ctk.CTkProgressBar(
            st, fg_color=C["input_bg"],
            progress_color=C["red"], height=6, corner_radius=0,
        )
        self.progress.set(0)
        self.progress.pack(fill="x", pady=(6, 2))

        # ── Brand stamp (bottom) ──
        stamp = ctk.CTkFrame(parent, fg_color=C["brown_dim"], corner_radius=0, height=38)
        stamp.pack(side="bottom", fill="x")
        stamp.pack_propagate(False)
        # mini logo
        if self._logo_mini:
            ctk.CTkLabel(
                stamp, image=self._logo_mini, text="",
                fg_color="transparent",
            ).pack(side="left", padx=(4, 0))
        ctk.CTkLabel(
            stamp,
            text=" LA CASA DEL TRUCO\n Darkbyte & Dirty  //  2026",
            font=PF(7, "bold"), text_color=C["brown"],
            justify="left",
        ).pack(side="left", padx=4)
        ctk.CTkLabel(
            stamp, text="◆", font=PF(14), text_color=C["red"]
        ).pack(side="right", padx=8)

    # ─────────────────────────────────────────────────────────────────────────
    # Actions
    # ─────────────────────────────────────────────────────────────────────────

    def _test_connection(self):
        self.conn_status.configure(text=">> TESTING...", text_color=C["warn"])
        threading.Thread(target=self._do_test_connection, daemon=True).start()

    def _do_test_connection(self):
        try:
            host = self.smtp_host.get().strip()
            port = int(self.smtp_port.get())
            user = self.smtp_user.get().strip()
            pwd  = self.smtp_pass.get()
            enc  = self.encryption.get()
            to   = int(self.smtp_timeout.get() or 10)
            if not host:
                raise ValueError("SMTP server is required.")
            srv = self._make_smtp(host, port, enc, to)
            if user:
                srv.login(user, pwd)
            srv.quit()
            self.after(0, lambda: self.conn_status.configure(
                text=">> OK — CONNECTED", text_color=C["ok"]))
            self._log("[OK] Test connection succeeded")
        except Exception as ex:
            msg = str(ex)[:60]
            self.after(0, lambda: self.conn_status.configure(
                text=f">> ERR: {msg}", text_color=C["err"]))
            self._log(f"[ERR] Test failed: {ex}")

    def _make_smtp(self, host, port, enc, timeout=10):
        # Ignore SSL validation to allow connecting to any SMTP regardless of certificate
        ctx = ssl.create_default_context()
        ctx.check_hostname = False
        ctx.verify_mode = ssl.CERT_NONE

        if enc == "SSL/TLS":
            return smtplib.SMTP_SSL(host, port, context=ctx, timeout=timeout)
        srv = smtplib.SMTP(host, port, timeout=timeout)
        srv.ehlo()
        if enc == "TLS (STARTTLS)":
            srv.starttls(context=ctx)
            srv.ehlo()
        return srv

    def _import_recipients(self):
        path = filedialog.askopenfilename(
            filetypes=[("Text/CSV", "*.txt *.csv"), ("All", "*.*")])
        if not path:
            return
        try:
            if path.lower().endswith(".csv"):
                emails = []
                with open(path, newline="", encoding="utf-8-sig") as f:
                    for row in csv.reader(f):
                        for cell in row:
                            emails += extract_emails(cell)
                raw = "\n".join(emails)
            else:
                with open(path, encoding="utf-8-sig") as f:
                    raw = f.read()
            cur = self.recipients_text.get("1.0", "end").rstrip("\n")
            self.recipients_text.delete("1.0", "end")
            self.recipients_text.insert("1.0", cur + "\n" + raw)
            self._update_recipient_count()
            self._log(f"[IMPORT] {os.path.basename(path)}")
        except Exception as ex:
            messagebox.showerror("Import Error", str(ex))

    def _clear_recipients(self):
        self.recipients_text.delete("1.0", "end")
        self._update_recipient_count()

    def _update_recipient_count(self, event=None):
        raw = self.recipients_text.get("1.0", "end")
        n = len(extract_emails(raw))
        self.recipient_counter.configure(text=f"[ {n} ]")
        self.total_recipients = n

    def _pick_attachment(self):
        p = filedialog.askopenfilename()
        if p:
            self.attachment_path.set(p)
            self.att_label.configure(
                text=os.path.basename(p)[:32], text_color=C["brown"])

    def _remove_attachment(self):
        self.attachment_path.set("")
        self.att_label.configure(text="-- no file --", text_color=C["gray"])

    # ── Send ──────────────────────────────────────────────────────────────────
    def _start_sending(self):
        emails = extract_emails(self.recipients_text.get("1.0", "end"))
        if not emails:
            messagebox.showwarning("D-D Mailer", "No valid recipients found.")
            return
        if not self.smtp_host.get().strip():
            messagebox.showwarning("D-D Mailer", "Configure SMTP server first.")
            return
        if not self.subject.get().strip():
            messagebox.showwarning("D-D Mailer", "Subject cannot be empty.")
            return

        self.sent_count = self.ok_count = self.err_count = 0
        self.total_recipients = len(emails)
        self._update_stats()
        self.progress.set(0)
        self._stop_event.clear()
        self._pause_event.set()

        self.btn_send.configure(state="disabled")
        self.btn_pause.configure(state="normal")
        self.btn_stop.configure(state="normal")

        self._send_thread = threading.Thread(
            target=self._send_bulk, args=(emails,), daemon=True)
        self._send_thread.start()

    def _send_bulk(self, emails):
        host      = self.smtp_host.get().strip()
        port      = int(self.smtp_port.get())
        enc       = self.encryption.get()
        timeout   = int(self.smtp_timeout.get() or 10)
        user      = self.smtp_user.get().strip()
        pwd       = self.smtp_pass.get()
        subj      = self.subject.get().strip()
        body      = self.body_text.get("1.0", "end").strip()
        html      = self.html_var.get()
        fname     = self.from_name.get().strip()
        att       = self.attachment_path.get()
        retry_max = int(self.retry_limit.get() or 3)
        d_lo      = float(self.delay_min.get() or 1)
        d_hi      = float(self.delay_max.get() or 3)
        every_n   = int(self.pause_every.get() or 10)
        priority  = self.priority.get()
        total     = len(emails)

        self._log(f"[START] {total} recipients")
        server = None

        def ensure():
            nonlocal server
            try:
                if server:
                    server.noop()
                    return server
            except Exception:
                server = None
            try:
                server = self._make_smtp(host, port, enc, timeout)
                if user:
                    server.login(user, pwd)
                self._log("[SMTP] Connected")
            except Exception as ex:
                self._log(f"[ERR] SMTP: {ex}")
                server = None
            return server

        for idx, email in enumerate(emails):
            if self._stop_event.is_set():
                self._log("[STOP] Aborted by user")
                break
            self._pause_event.wait()

            if every_n > 0 and idx > 0 and idx % every_n == 0:
                sl = random.uniform(d_lo * 2, d_hi * 2)
                self._log(f"[PAUSE] Batch pause {sl:.1f}s")
                time.sleep(sl)

            ok = False
            for attempt in range(1, retry_max + 1):
                if self._stop_event.is_set():
                    break
                srv = ensure()
                if srv is None:
                    time.sleep(2)
                    continue
                try:
                    msg = self._build_message(user, fname, email,
                                               subj, body, html, att, priority)
                    srv.sendmail(user, email, msg.as_string())
                    self.ok_count += 1
                    self._log(f"[OK] ({idx+1}/{total}) {email}")
                    ok = True
                    break
                except smtplib.SMTPRecipientsRefused:
                    self._log(f"[REFUSED] {email}")
                    break
                except Exception as ex:
                    self._log(f"[RETRY {attempt}] {email}: {ex}")
                    server = None
                    time.sleep(1)

            self.sent_count += 1
            if not ok:
                self.err_count += 1

            prog = self.sent_count / total
            self.after(0, self._update_stats)
            self.after(0, lambda p=prog: self.progress.set(p))

            if not self._stop_event.is_set():
                time.sleep(random.uniform(d_lo, d_hi))

        if server:
            try:
                server.quit()
            except Exception:
                pass

        self._log(
            f"[DONE] Sent:{self.sent_count}  OK:{self.ok_count}  ERR:{self.err_count}")
        self.after(0, self._on_send_finished)

    def _build_message(self, from_addr, from_name, to_addr,
                        subject, body, html, att_path, priority):
        import socket
        import quopri

        # Format sender properly
        sender = formataddr((from_name, from_addr)) if from_name else from_addr
        
        # Use local hostname to avoid spoofing the sender's domain in Message-ID
        domain = socket.gethostname()
        
        # Decide the root structure based on attachments
        if att_path and os.path.isfile(att_path):
            msg = MIMEMultipart("mixed")
        elif html:
            msg = MIMEMultipart("alternative")
        else:
            msg = MIMEText("", "plain", "utf-8")
            msg.set_payload(quopri.encodestring(body.encode("utf-8")).decode("ascii"))
            del msg["Content-Transfer-Encoding"]
            msg["Content-Transfer-Encoding"] = "quoted-printable"
            
        msg["From"]       = sender
        msg["To"]         = to_addr
        msg["Subject"]    = subject
        msg["Date"]       = formatdate(localtime=True)
        msg["Message-ID"] = make_msgid(domain=domain)

        if priority == "High":
            msg["X-Priority"]        = "1"
            msg["X-MSMail-Priority"] = "High"
            msg["Importance"]        = "High"

        # Helper to create quoted-printable text parts
        def qp_mime(text, subtype):
            part = MIMEText("", subtype, "utf-8")
            part.set_payload(quopri.encodestring(text.encode("utf-8")).decode("ascii"))
            del part["Content-Transfer-Encoding"]
            part["Content-Transfer-Encoding"] = "quoted-printable"
            return part

        # Construct the body payload
        if html:
            # Build plain text fallback
            plain_text = re.sub(r"<br\s*/?>|<\/p>|<\/div>|<\/h[1-6]>", "\n", body, flags=re.IGNORECASE)
            plain_text = re.sub(r"<[^>]+>", "", plain_text)
            plain_text = html_lib.unescape(plain_text).strip()
            
            # Ensure HTML has a doctype if missing (anti-spam best practice)
            safe_html = body
            if "<html" in safe_html.lower() and "<!doctype" not in safe_html.lower():
                safe_html = "<!DOCTYPE html>\n" + safe_html
                
            part_plain = qp_mime(plain_text, "plain")
            part_html = qp_mime(safe_html, "html")
            
            if att_path and os.path.isfile(att_path):
                # When using mixed, the text body MUST be grouped in an alternative sub-part
                alt = MIMEMultipart("alternative")
                alt.attach(part_plain)
                alt.attach(part_html)
                msg.attach(alt)
            else:
                # Root is alternative
                msg.attach(part_plain)
                msg.attach(part_html)
        else:
            # Not HTML
            if att_path and os.path.isfile(att_path):
                # Root is mixed, just attach the plain text directly
                msg.attach(qp_mime(body, "plain"))

        if att_path and os.path.isfile(att_path):
            with open(att_path, "rb") as fh:
                part = MIMEBase("application", "octet-stream")
                part.set_payload(fh.read())
            encoders.encode_base64(part)
            part.add_header(
                "Content-Disposition",
                f'attachment; filename="{os.path.basename(att_path)}"')
            msg.attach(part)
            
        return msg

    def _toggle_pause(self):
        if self._pause_event.is_set():
            self._pause_event.clear()
            self.btn_pause.configure(
                text="[ ▶  RESUME ]",
                fg_color=C["red_dim"], text_color=C["red"],
                border_color=C["red_dark"],
            )
            self._log("[PAUSE]")
        else:
            self._pause_event.set()
            self.btn_pause.configure(
                text="[ ⏸  PAUSE ]",
                fg_color=C["card"], text_color=C["gray_light"],
                border_color=C["gray_dark"],
            )
            self._log("[RESUME]")

    def _stop_sending(self):
        self._stop_event.set()
        self._pause_event.set()
        self._log("[STOP] Signal sent…")

    def _on_send_finished(self):
        self.btn_send.configure(state="normal")
        self.btn_pause.configure(
            state="disabled", text="[ ⏸  PAUSE ]",
            fg_color=C["card"], text_color=C["gray_light"])
        self.btn_stop.configure(state="disabled")

    def _update_stats(self):
        self.lbl_total.configure(text=str(self.total_recipients))
        self.lbl_sent.configure(text=str(self.sent_count))
        self.lbl_ok.configure(text=str(self.ok_count))
        self.lbl_err.configure(text=str(self.err_count))

    def _log(self, msg):
        self._log_queue.put(f"[{ts()}] {msg}")

    def _poll_log(self):
        try:
            while True:
                m = self._log_queue.get_nowait()
                self.log_box.configure(state="normal")
                self.log_box.insert("end", m + "\n")
                self.log_box.see("end")
                self.log_box.configure(state="disabled")
        except queue.Empty:
            pass
        self.after(120, self._poll_log)

    def _clear_log(self):
        self.log_box.configure(state="normal")
        self.log_box.delete("1.0", "end")
        self.log_box.configure(state="disabled")


# ─── Entry ────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
    app = MassMailerApp()
    app.mainloop()
