import tkinter as tk
from tkinter import messagebox, filedialog
import threading
import asyncio
import os
import sys
import time
import random
import json
import math
import re
from tkinterdnd2 import DND_FILES, TkinterDnD
import customtkinter as ctk

# Golden Ratio Configuration
GOLDEN_RATIO = 1.6180339887
WIDTH = 433
HEIGHT = 600

SESSION_FILE = "session_state.json"

# Industrial 80s Palette
INDUSTRIAL_GREY = "#1F2124"
DARK_PLATE = "#111214"
GLOW_GREEN = "#39FF14"
GOLD_PLATE = "#D4AF37"
NES_RED = "#B22222"
VINTAGE_WHITE = "#E0E0E0"

# Theme setup for pure aesthetics
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("dark-blue")

# Load logic module
from importlib.util import spec_from_file_location, module_from_spec

def load_logic():
    try:
        logic_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "2.0.py")
        spec = spec_from_file_location("checker_logic", logic_path)
        module = module_from_spec(spec)
        spec.loader.exec_module(module)
        return module
    except Exception as e:
        print(f"Error loading 2.0.py: {e}")
        return None

checker_module = load_logic()
Account = checker_module.Account if checker_module else None
EmailChecker = checker_module.EmailChecker if checker_module else None

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

class TkinterDnD_CTk(ctk.CTk, TkinterDnD.DnDWrapper):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.TkdndVersion = TkinterDnD._require(self)

class GoldenRetroGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("CASA DEL TRUCO 2.0 - GOLDEN PROPORTIONS")
        self.root.geometry(f"{WIDTH}x{HEIGHT}")
        self.root.resizable(False, False)
        self.root.configure(fg_color=DARK_PLATE)
        
        # Internal State
        self.running = False
        self.input_file = None
        self.valid_accounts_list = []
        self.processed_count = 0
        
        self.viz_angle = 0
        self.viz_loop_id = None
        
        # Checking license status
        self.is_licensed = False
        if license_manager:
            self.is_licensed = license_manager.is_activated()
        else:
            # Fallback safe behavior if manager is missing
            self.is_licensed = True

        if not self.is_licensed:
            self.build_activation_ui()
        else:
            self.build_main_ui()

    def build_activation_ui(self):
        # 1. Header
        self.header = ctk.CTkFrame(self.root, height=40, corner_radius=0, fg_color=NES_RED)
        self.header.pack(fill="x")
        self.header.pack_propagate(False)
        ctk.CTkLabel(self.header, text="CORE LOCKED - ACTIVATION REQUIRED", text_color=VINTAGE_WHITE, 
                     font=ctk.CTkFont(family="Courier", size=14, weight="bold")).pack(pady=8)

        # 2. Main Lock Panel
        self.lock_panel = ctk.CTkFrame(self.root, fg_color=INDUSTRIAL_GREY, corner_radius=8, border_width=2, border_color=NES_RED)
        self.lock_panel.pack(fill="both", expand=True, padx=20, pady=25)

        # Lock Icon or Label
        ctk.CTkLabel(self.lock_panel, text="🔑", font=ctk.CTkFont(size=50)).pack(pady=(20, 10))
        
        ctk.CTkLabel(self.lock_panel, text="CIENBRAZOS CORE ENFORCEMENT", text_color=NES_RED,
                     font=ctk.CTkFont(family="Courier", size=15, weight="bold")).pack(pady=(0, 20))

        # HWID Section
        ctk.CTkLabel(self.lock_panel, text="YOUR SYSTEM MACHINE ID (HWID):", text_color=VINTAGE_WHITE,
                     font=ctk.CTkFont(family="Courier", size=11, weight="bold")).pack(anchor="w", padx=20)

        hwid = license_manager.get_machine_id() if license_manager else "UNKNOWN_HWID"
        
        self.hwid_entry = ctk.CTkEntry(self.lock_panel, fg_color=DARK_PLATE, text_color=GLOW_GREEN,
                                       font=ctk.CTkFont("Consolas", 11), border_width=1, border_color="#444444")
        self.hwid_entry.insert(0, hwid)
        self.hwid_entry.configure(state="readonly")
        self.hwid_entry.pack(fill="x", padx=20, pady=(2, 8))

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

        self.btn_copy_hwid = ctk.CTkButton(self.lock_panel, text="[ COPY HWID ]", font=ctk.CTkFont("Courier", 11, "bold"),
                                           fg_color="#2A2A2D", hover_color=GOLD_PLATE, height=28, command=copy_hwid)
        self.btn_copy_hwid.pack(padx=20, pady=(0, 20))

        # License Key Section
        ctk.CTkLabel(self.lock_panel, text="PASTE LICENSE KEY:", text_color=VINTAGE_WHITE,
                     font=ctk.CTkFont(family="Courier", size=11, weight="bold")).pack(anchor="w", padx=20)

        self.license_entry = ctk.CTkEntry(self.lock_panel, fg_color=DARK_PLATE, text_color=GLOW_GREEN,
                                          font=ctk.CTkFont("Consolas", 11), border_width=1, border_color="#444444")
        self.license_entry.pack(fill="x", padx=20, pady=(2, 15))

        def attempt_activation():
            key = self.license_entry.get().strip()
            if not key:
                messagebox.showerror("Activation Failed", "Please enter a license key.")
                return
            if license_manager and license_manager.save_license(key):
                messagebox.showinfo("Success", "Core activated successfully. Access granted.")
                # Clear entire UI and rebuild main UI
                for widget in self.root.winfo_children():
                    widget.destroy()
                self.build_main_ui()
            else:
                messagebox.showerror("Activation Failed", "Invalid License Key for this Machine ID.")

        self.btn_activate = ctk.CTkButton(self.lock_panel, text="ACTIVATE ENGINES", font=ctk.CTkFont("Courier", 13, "bold"),
                                          fg_color="#1a2e1a", hover_color=GLOW_GREEN, border_width=2, border_color=GLOW_GREEN,
                                          height=40, command=attempt_activation)
        self.btn_activate.pack(fill="x", padx=20, pady=(10, 0))

        # Footer inside locking
        self.footer = ctk.CTkFrame(self.root, height=21, fg_color=DARK_PLATE, corner_radius=0)
        self.footer.pack(fill="x", side="bottom")
        ctk.CTkLabel(self.footer, text="FIRMWARE BY DARKBYTE v2.2 - RETRO INDUSTRIAL", text_color="#555555", font=ctk.CTkFont("Courier", 10)).pack(pady=2)

    def build_main_ui(self):
        self.build_ui()
        self.animate_viz()
        self.check_resume_session()

    def build_ui(self):
        # 1. Header
        self.header = ctk.CTkFrame(self.root, height=40, corner_radius=0, fg_color=NES_RED)
        self.header.pack(fill="x")
        self.header.pack_propagate(False)
        ctk.CTkLabel(self.header, text="LA CASA DEL TRUCO 2.0", text_color=VINTAGE_WHITE, 
                     font=ctk.CTkFont(family="Courier", size=16, weight="bold")).pack(pady=8)
        
        # 2. Top Settings Panel
        self.settings = ctk.CTkFrame(self.root, fg_color=INDUSTRIAL_GREY, corner_radius=8, border_width=1, border_color="#333333")
        self.settings.pack(fill="x", padx=13, pady=(13, 8))
        
        ctk.CTkLabel(self.settings, text="TEST DESTINATIONS (comma sep):", text_color=VINTAGE_WHITE, font=ctk.CTkFont(family="Courier", size=11, weight="bold")).pack(anchor="w", padx=8, pady=(5,0))
        self.test_email_entry = ctk.CTkTextbox(self.settings, height=40, fg_color=DARK_PLATE, text_color=GLOW_GREEN, font=ctk.CTkFont("Consolas", 11), border_width=1, border_color="#444444")
        self.test_email_entry.pack(fill="x", padx=8, pady=(2, 5))
        
        ctk.CTkLabel(self.settings, text="CUSTOM TEST MESSAGE:", text_color=VINTAGE_WHITE, font=ctk.CTkFont(family="Courier", size=11, weight="bold")).pack(anchor="w", padx=8)
        self.test_message_entry = ctk.CTkTextbox(self.settings, height=50, fg_color=DARK_PLATE, text_color=GLOW_GREEN, font=ctk.CTkFont("Consolas", 11), border_width=1, border_color="#444444")
        self.test_message_entry.pack(fill="x", padx=8, pady=(2, 8))
        
        # 3. Monitor Area (SHRUNK to balance space)
        self.monitor = ctk.CTkFrame(self.root, height=110, fg_color=INDUSTRIAL_GREY, corner_radius=8, border_width=1, border_color="#333333")
        self.monitor.pack(fill="x", padx=13, pady=(0, 8))
        self.monitor.pack_propagate(False)
        
        self.screen = ctk.CTkFrame(self.monitor, fg_color=DARK_PLATE, corner_radius=5)
        self.screen.pack(fill="both", expand=True, padx=8, pady=8)
        
        self.track_label = ctk.CTkLabel(self.screen, text="SYSTEM STATUS: STANDBY", 
                                        text_color=GOLD_PLATE, font=ctk.CTkFont(family="Courier", size=13, weight="bold"))
        self.track_label.pack(pady=(8, 0))
        
        self.viz_canvas = tk.Canvas(self.screen.master, bg=DARK_PLATE, height=40, highlightthickness=0)
        self.viz_canvas.place(in_=self.screen, relx=0.05, rely=0.35, relwidth=0.9, relheight=0.3)
        self.viz_line = self.viz_canvas.create_line(0, 20, WIDTH, 20, fill=GLOW_GREEN, width=2)
        
        self.stat_frame = ctk.CTkFrame(self.screen, fg_color="transparent")
        self.stat_frame.pack(side="bottom", fill="x", pady=8, padx=13)
        
        self.stat_v = ctk.CTkLabel(self.stat_frame, text="OK: 0", text_color=GLOW_GREEN, font=ctk.CTkFont(family="Courier", size=13, weight="bold"))
        self.stat_v.pack(side="left", expand=True)
        self.stat_s = ctk.CTkLabel(self.stat_frame, text="SCAN: 0", text_color=GOLD_PLATE, font=ctk.CTkFont(family="Courier", size=13, weight="bold"))
        self.stat_s.pack(side="left", expand=True)
        self.stat_i = ctk.CTkLabel(self.stat_frame, text="DEAD: 0", text_color=NES_RED, font=ctk.CTkFont(family="Courier", size=13, weight="bold"))
        self.stat_i.pack(side="left", expand=True)

        # 4. Controls
        self.rack = ctk.CTkFrame(self.root, height=45, fg_color="transparent")
        self.rack.pack(fill="x", padx=13)
        self.rack.pack_propagate(False)
        
        self.btn_play = ctk.CTkButton(self.rack, text="▶ PLAY", font=ctk.CTkFont(family="Courier", size=12, weight="bold"), fg_color="#2A2A2D", hover_color=GOLD_PLATE, command=self.start_check)
        self.btn_play.pack(side="left", expand=True, fill="both", padx=(0, 5))
        self.btn_stop = ctk.CTkButton(self.rack, text="⏹ STOP", font=ctk.CTkFont(family="Courier", size=12, weight="bold"), fg_color="#2A2A2D", hover_color=NES_RED, command=self.stop_check)
        self.btn_stop.pack(side="left", expand=True, fill="both", padx=(0, 5))
        self.btn_reset = ctk.CTkButton(self.rack, text="⟲ RESET", font=ctk.CTkFont(family="Courier", size=12, weight="bold"), fg_color="#2A2A2D", hover_color="#555555", command=self.reset_system)
        self.btn_reset.pack(side="left", expand=True, fill="both")

        # 4.5 MASTER COPY BUTTON (MAX VISIBILITY)
        self.btn_copy_master = ctk.CTkButton(self.root, text=">>> COPY ALL VERIFIED ACCESS <<<", 
                                             font=ctk.CTkFont("Courier", 13, "bold"), 
                                             fg_color="#1a2e1a", hover_color=GLOW_GREEN, 
                                             border_width=2, border_color=GLOW_GREEN, 
                                             height=40, command=self.copy_results)
        self.btn_copy_master.pack(fill="x", padx=13, pady=(8, 0))

        # 5. Logs & Drop Zone (EXPANDED)
        self.playlist_frame = ctk.CTkFrame(self.root, fg_color=INDUSTRIAL_GREY, corner_radius=8, border_width=1, border_color="#333333")
        self.playlist_frame.pack(fill="both", expand=True, padx=13, pady=(8, 13))
        
        ctk.CTkLabel(self.playlist_frame, text="--- VERIFIED ACCESS ---", text_color=GOLD_PLATE, font=ctk.CTkFont(family="Courier", size=12, weight="bold")).pack(pady=8)
        
        self.log_box = ctk.CTkTextbox(self.playlist_frame, fg_color=DARK_PLATE, text_color=GLOW_GREEN, font=ctk.CTkFont("Consolas", 11), border_width=1, border_color="#444444")
        self.log_box.pack(fill="both", expand=True, padx=13, pady=(0, 8))
        
        self.log_box.drop_target_register(DND_FILES)
        self.log_box.dnd_bind('<<Drop>>', self.handle_drop)
        self.log_msg(">>> DROP UNIT FILE HERE <<<")
        
        self.actions = ctk.CTkFrame(self.playlist_frame, fg_color="transparent")
        self.actions.pack(fill="x", padx=13, pady=(0, 13))
        
        self.btn_exp = ctk.CTkButton(self.actions, text="E X P O R T", font=ctk.CTkFont("Courier", 12, "bold"), fg_color="#2A2A2D", hover_color=GLOW_GREEN, text_color_disabled=DARK_PLATE, command=self.export_results)
        self.btn_exp.pack(fill="x")
        
        # 6. Footer
        self.footer = ctk.CTkFrame(self.root, height=21, fg_color=DARK_PLATE, corner_radius=0)
        self.footer.pack(fill="x", side="bottom")
        ctk.CTkLabel(self.footer, text="FIRMWARE BY DARKBYTE v2.2 - RETRO INDUSTRIAL", text_color="#555555", font=ctk.CTkFont("Courier", 10)).pack(pady=2)

    def log_msg(self, msg):
        self.log_box.insert("end", msg + "\n")
        self.log_box.see("end")

    # --- SESSION DATA LAYER ---
    def save_session(self, current_index):
        if not self.input_file: return
        data = {
            "input_file": self.input_file,
            "last_index": current_index,
            "valids": self.valid_accounts_list,
            "stats": {"ok": self.stat_v.cget("text"), "dead": self.stat_i.cget("text")}
        }
        with open(SESSION_FILE, 'w') as f:
            json.dump(data, f)

    def load_session(self):
        if os.path.exists(SESSION_FILE):
            try:
                with open(SESSION_FILE, 'r') as f:
                    return json.load(f)
            except:
                pass
        return None

    def check_resume_session(self):
        session = self.load_session()
        if session and os.path.exists(session.get("input_file", "")):
            if messagebox.askyesno("BLACK BOX RECOVERY", f"PREVIOUS SESSION DETECTED:\n{os.path.basename(session['input_file'])}\n\nRESUME OPERATION?"):
                self.input_file = session["input_file"]
                self.processed_count = session["last_index"]
                self.valid_accounts_list = session["valids"]
                
                self.track_label.configure(text=f"RECOVERY: {os.path.basename(self.input_file)}")
                self.log_box.delete("0.0", "end")
                for v in self.valid_accounts_list:
                    self.log_msg(f" ✓ {v}")
                self.stat_v.configure(text=session["stats"]["ok"])
                self.stat_s.configure(text=f"SCAN: {self.processed_count}")
                self.stat_i.configure(text=session["stats"]["dead"])
                self.log_msg(f">>> RESUMING AT INDEX {self.processed_count} <<<")

    def handle_drop(self, event):
        file = event.data.strip('{}')
        if file.endswith('.txt'):
            self.input_file = file
            self.processed_count = 0
            self.valid_accounts_list = []
            if os.path.exists(SESSION_FILE): 
                try: os.remove(SESSION_FILE)
                except: pass
            
            # Contar lineas unicas
            if EmailChecker:
                temp_checker = EmailChecker()
                loaded = temp_checker.load_accounts(file)
                count = len(loaded)
            else:
                count = 0
            
            self.track_label.configure(text=f"UNIT LOADED: {os.path.basename(file)}")
            self.log_box.delete("0.0", "end")
            self.log_msg(f"UNIT CHECK COMPLETE. {count} UNIQUE ACCOUNTS FOUND.")
            self.log_msg("READY TO SCAN UNIT.")
        else:
            messagebox.showerror("IO ERROR", "DISK READ FAIL: USE .TXT")

    # --- CANVAS ANIMATION ---
    def animate_viz(self):
        points = []
        width = int(WIDTH * 0.8)
        amplitude = 30 if self.running else 3
        frequency = 0.15 if self.running else 0.05
        
        for x in range(0, width, 4):
            y = 20 + amplitude * math.sin(frequency * (x + self.viz_angle))
            if self.running:
                y += random.randint(-4, 4)
            points.extend([x, y])
            
        try:
            self.viz_canvas.coords(self.viz_line, *points)
        except Exception:
            pass
            
        self.viz_angle += 8 if self.running else 2
        self.viz_loop_id = self.root.after(50, self.animate_viz)

    # --- CALLBACK PROCESS ---
    def update_gui_callback(self, account, stats):
        processed = self.processed_count + stats['checked']
        total = stats.get('total', 1)
        try:
            self.stat_v.configure(text=f"OK: {stats['valid']}")
            self.stat_s.configure(text=f"SCAN: {processed}")
            self.stat_i.configure(text=f"DEAD: {stats['invalid'] + stats['auth_failed']}")
            
            if processed % 10 == 0:
                self.save_session(processed)
            
            percent = int((processed / max(total, 1)) * 100)

            if account.status == 'valid':
                server = getattr(account, 'server', 'Unknown')
                port = getattr(account, 'port', 'Unknown')
                info = f"{server}:{port} | {account.email}{getattr(account, 'sep', ':')}{account.password}"
                if getattr(account, 'error', '') and "Login:" in account.error:
                    url = account.error.split('Login: ')[-1]
                    info += f" -> {url}"
                
                self.log_msg(f" ✓ {info}")
                self.valid_accounts_list.append(info)
                self.track_label.configure(text=f"[{percent}%] EXPLOIT SUCCESS! {account.email[:25]}")
            else:
                self.track_label.configure(text=f"[{percent}%] SCAN MATRIX... {account.email[:25]}")
        except Exception as e:
            print("Update GUI Error:", e)

    # --- ACTION BUTTONS ---
    def copy_results(self):
        if not self.valid_accounts_list: return
        self.root.clipboard_clear()
        self.root.clipboard_append("\n".join(self.valid_accounts_list))
        messagebox.showinfo("CLIPBOARD", "VALIDS COPIED TO MEMORY.")

    def export_results(self):
        if not self.valid_accounts_list: return
        path = filedialog.asksaveasfilename(defaultextension=".txt", initialfile="Verified_Access.txt")
        if path:
            with open(path, 'w') as f: f.write("\n".join(self.valid_accounts_list))
            messagebox.showinfo("IO SUCCESS", "ACCESS LIST EXPORTED.")

    def start_check(self):
        if self.running or not self.input_file: return
        self.running = True
        self.track_label.configure(text="SYSTEM ACTIVE...")
        self.btn_play.configure(state="disabled")
        
        def run_thread():
            asyncio.run(self.async_wrapper())
        threading.Thread(target=run_thread, daemon=True).start()

    async def async_wrapper(self):
        if not EmailChecker: 
            self.log_msg("FATAL ERROR: 2.0.py logic module not loaded!")
            self.running = False
            self.root.after(0, self.finish_ui)
            return
            
        test_input = self.test_email_entry.get("0.0", "end").strip()
        test_emails = [e.strip() for e in re.split(r'[,\n\s]+', test_input) if '@' in e]
        
        test_message_body = self.test_message_entry.get("0.0", "end").strip()
        if not test_message_body: test_message_body = None
        
        # Polling function to cleanly interrupt thread if GUI calls STOP
        def check_stop(): return not self.running
        
        checker = EmailChecker(
            on_account_checked=lambda a, s: self.root.after(0, self.update_gui_callback, a, s),
            test_emails=test_emails,
            test_message_body=test_message_body,
            should_stop=check_stop
        )
        accounts = checker.load_accounts(self.input_file)
        checker.stats['total'] = len(accounts)
        
        if self.processed_count > 0:
            accounts = accounts[self.processed_count:]
            
        await checker.process_batch(accounts, mode="smtp")
        self.running = False
        self.root.after(0, self.finish_ui)

    def finish_ui(self):
        self.btn_play.configure(state="normal")
        self.track_label.configure(text="[100%] EXPLOIT COMPLETE. GHOST IN THE MACHINE.")
        if os.path.exists(SESSION_FILE): 
            try: os.remove(SESSION_FILE)
            except: pass
        if self.valid_accounts_list:
            # Auto-copy to clipboard
            try:
                self.root.clipboard_clear()
                self.root.clipboard_append("\n".join(self.valid_accounts_list))
                status_msg = "RESULTS AUTO-COPIED TO CLIPBOARD."
            except:
                status_msg = "VALIDS READY IN MEMORY."

            messagebox.showinfo("Mr. Robot", f"Hello, friend.\n\nWe found {len(self.valid_accounts_list)} doors open.\n{status_msg}\nYou know what to do.")
            self.log_msg(f">>> END OF LINE. {status_msg} <<<")

    def stop_check(self):
        self.running = False
        self.track_label.configure(text="MANUAL OVERRIDE: HALTED")
        self.btn_play.configure(state="normal")
        self.save_session(self.processed_count)

    def reset_system(self):
        if messagebox.askyesno("MASTER RESET", "PERMANENTLY WIPE ALL DATA AND START FRESH?"):
            self.running = False
            self.input_file = None
            self.valid_accounts_list = []
            self.processed_count = 0
            if os.path.exists(SESSION_FILE): 
                try: os.remove(SESSION_FILE)
                except: pass
            
            self.log_box.delete("0.0", "end")
            self.log_msg(">>> SYSTEM RESET COMPLETE <<<")
            self.log_msg(">>> DROP NEW UNIT HERE <<<")
            self.stat_v.configure(text="OK: 0")
            self.stat_s.configure(text="SCAN: 0")
            self.stat_i.configure(text="DEAD: 0")
            self.track_label.configure(text="SYSTEM STATUS: READY")
            self.btn_play.configure(state="normal")

if __name__ == "__main__":
    if os.name == 'nt':
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    root = TkinterDnD_CTk()
    app = GoldenRetroGUI(root)
    root.mainloop()
