import sys
import json
import types
import os
import re
import threading
import tkinter as tk
import hashlib
from typing import Optional
# --- ensure imghdr exists before any library that might import it ---
try:
    import imghdr
except ModuleNotFoundError:
    imghdr = types.ModuleType('imghdr')
    def what(fp, h=None):
        if h is None:
            with open(fp, 'rb') as f:
                h = f.read(32)
        sigs = {b'\x89PNG\r\n\x1a\n': 'png',
                b'\xff\xd8\xff': 'jpeg',
                b'GIF87a': 'gif',
                b'GIF89a': 'gif'}
        for sig, fmt in sigs.items():
            if h.startswith(sig):
                return fmt
        return None
    imghdr.what = what
    sys.modules['imghdr'] = imghdr
# -------------------------------------------------------------------

from tkinter import ttk, filedialog, messagebox, scrolledtext, simpledialog as sd
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

try:
    from . import backend
except ImportError:  # pragma: no cover - support direct execution
    import backend  # type: ignore

try:
    from .firebase_sync import EmailNotVerifiedError, FirebaseAuthError
except ImportError:  # pragma: no cover
    from firebase_sync import EmailNotVerifiedError, FirebaseAuthError  # type: ignore

try:
    from .passwords import generate_password
except ImportError:  # pragma: no cover - support direct execution
    from passwords import generate_password  # type: ignore

# Convenience aliases for dense UI code paths.
store_master_hash = backend.store_master_hash
verify_master_password = backend.verify_master_password
store_key = backend.store_key
get_stored_key = backend.get_stored_key
load_credentials = backend.load_credentials
save_credentials = backend.save_credentials
load_keyvault = backend.load_keyvault
save_keyvault = backend.save_keyvault
load_passphrase_from_vault = backend.load_passphrase_from_vault
load_key_from_vault = backend.load_key_from_vault
list_vault_keys = backend.list_vault_keys
load_cards = backend.load_cards
save_cards = backend.save_cards
load_public_key_from_text = backend.load_public_key_from_text
load_private_key_from_text = backend.load_private_key_from_text
encrypt_message = backend.encrypt_message
decrypt_message = backend.decrypt_message
sign_message_detached = backend.sign_message_detached
verify_signature = backend.verify_signature
generate_keypair = backend.generate_keypair
valid_card = backend.valid_card
parse_expiration = backend.parse_expiration
derive_aes_key = backend.derive_aes_key
aes_encrypt = backend.aes_encrypt
aes_decrypt = backend.aes_decrypt
gpg = backend.gpg

public_key_combo = private_key_combo = sign_key_combo = verify_key_combo = None
public_key_var = private_key_var = sign_key_var = verify_key_var = None
vault_tree = vault_private_text = vault_public_text = None
vault_passphrase_var = None

def refresh_key_dropdowns():
    global public_key_combo, private_key_combo, sign_key_combo, verify_key_combo
    global public_key_var, private_key_var, sign_key_var, verify_key_var
    if public_key_combo is None:
        return
    public_keys = backend.list_vault_keys('public')
    private_keys = backend.list_vault_keys('private')
    for combo, var, values in (
        (public_key_combo, public_key_var, public_keys),
        (verify_key_combo, verify_key_var, public_keys),
        (private_key_combo, private_key_var, private_keys),
        (sign_key_combo, sign_key_var, private_keys),
    ):
        if combo is None or var is None:
            continue
        combo['values'] = values
        if var.get() not in values:
            var.set(values[0] if values else '')

def load_selected_key_into(widget, kind, var, passphrase_target=None):
    if widget is None or var is None:
        return
    fp = var.get()
    if not fp:
        messagebox.showerror('Error','No key selected in vault')
        return
    material = backend.load_key_from_vault(fp, kind)
    if not material:
        messagebox.showerror('Error','Selected key material is not available')
        return
    widget.delete('1.0', tk.END)
    widget.insert('1.0', material)
    if kind == 'private' and passphrase_target is not None:
        stored = backend.load_passphrase_from_vault(fp)
        passphrase_target.delete(0, tk.END)
        if stored:
            passphrase_target.insert(0, stored)

def update_vault_detail(fp=None):
    global vault_private_text, vault_public_text, vault_passphrase_var
    if vault_private_text is None or vault_public_text is None or vault_passphrase_var is None:
        return
    if not fp:
        vault_private_text.delete('1.0', tk.END)
        vault_public_text.delete('1.0', tk.END)
        vault_passphrase_var.set('')
        return
    entry = backend.load_keyvault().get(fp, {})
    vault_private_text.delete('1.0', tk.END)
    vault_public_text.delete('1.0', tk.END)
    if 'private' in entry:
        vault_private_text.insert('1.0', entry['private'])
    if 'public' in entry:
        vault_public_text.insert('1.0', entry['public'])
    vault_passphrase_var.set(entry.get('passphrase', ''))

def refresh_vault_view(selected=None):
    global vault_tree
    if vault_tree is None:
        return
    for item in vault_tree.get_children():
        vault_tree.delete(item)
    for fp, data in sorted(backend.load_keyvault().items()):
        vault_tree.insert(
            '',
            'end',
            iid=fp,
            values=(
                fp,
                'Yes' if 'public' in data else 'No',
                'Yes' if 'private' in data else 'No',
                'Yes' if data.get('passphrase') else 'No',
            ),
        )
    if selected and vault_tree.exists(selected):
        vault_tree.selection_set(selected)
        vault_tree.focus(selected)
    elif vault_tree.get_children():
        first = vault_tree.get_children()[0]
        vault_tree.selection_set(first)
        vault_tree.focus(first)
        selected = first
    else:
        selected = None
    update_vault_detail(selected)
    refresh_key_dropdowns()

def on_vault_select(event=None):
    if vault_tree is None:
        return
    sel = vault_tree.selection()
    fp = sel[0] if sel else None
    update_vault_detail(fp)

def import_public_key_from_file(store_slot=None, target_widget=None):
    path = filedialog.askopenfilename(filetypes=[('PGP Public Key', '*.asc *.pgp'), ('All files', '*.*')])
    if not path:
        return None
    with open(path) as fh:
        key_text = fh.read()
    try:
        fp = backend.load_public_key_from_text(key_text)
    except Exception as exc:
        messagebox.showerror('Error', str(exc))
        return None
    if store_slot:
        backend.store_key(store_slot, key_text)
    if target_widget is not None:
        target_widget.delete('1.0', tk.END)
        target_widget.insert('1.0', key_text)
    refresh_vault_view(fp)
    messagebox.showinfo('Imported', f'Public key stored with fingerprint {fp}')
    return fp

def import_private_key_from_file(store_slot=None, target_widget=None, passphrase_source=None):
    path = filedialog.askopenfilename(filetypes=[('PGP Private Key', '*.asc *.pgp'), ('All files', '*.*')])
    if not path:
        return None
    with open(path) as fh:
        key_text = fh.read()
    passphrase = passphrase_source.get() if passphrase_source is not None else None
    if passphrase_source is None:
        passphrase = sd.askstring('Passphrase', 'Enter private key passphrase (leave blank if none):', show='*')
    try:
        fp = backend.load_private_key_from_text(key_text, passphrase=passphrase or None, persist_bucket=store_slot)
    except Exception as exc:
        messagebox.showerror('Error', str(exc))
        return None
    if passphrase_source is not None:
        passphrase_source.delete(0, tk.END)
        if passphrase:
            passphrase_source.insert(0, passphrase)
    if target_widget is not None:
        target_widget.delete('1.0', tk.END)
        target_widget.insert('1.0', key_text)
    refresh_vault_view(fp)
    messagebox.showinfo('Imported', f'Private key stored with fingerprint {fp}')
    return fp

def export_vault_key(fp, kind):
    material = backend.load_key_from_vault(fp, kind)
    if not material:
        messagebox.showerror('Error', f'No {kind} key material for the selected fingerprint')
        return
    default_ext = '.asc'
    title = f'Export {kind.capitalize()} Key'
    path = filedialog.asksaveasfilename(defaultextension=default_ext, filetypes=[('PGP Key', '*.asc *.pgp'), ('All files', '*.*')], title=title)
    if not path:
        return
    with open(path, 'w') as fh:
        fh.write(material)
    with open(path + '.fingerprint', 'w') as fh:
        fh.write(fp)
    messagebox.showinfo('Exported', f'{kind.capitalize()} key saved to {path}')

def delete_vault_entries(fingerprints):
    vault = backend.load_keyvault()
    changed = False
    for fp in fingerprints:
        if vault.pop(fp, None) is not None:
            changed = True
    if changed:
        backend.save_keyvault(vault)
        refresh_key_dropdowns()
        refresh_vault_view()

def export_selected_from_combo(kind, var):
    if var is None:
        return
    fp = var.get()
    if not fp:
        messagebox.showerror('Error', 'No key selected in vault')
        return
    export_vault_key(fp, kind)

def save_selected_passphrase():
    if vault_tree is None or vault_passphrase_var is None:
        return
    sel = vault_tree.selection()
    if not sel:
        messagebox.showerror('Error', 'No key selected')
        return
    fp = sel[0]
    if backend.store_passphrase(fp, vault_passphrase_var.get()):
        refresh_vault_view(fp)
        messagebox.showinfo('Saved', 'Passphrase updated for selected key')
    else:
        messagebox.showinfo('No Change', 'Passphrase unchanged')

def clear_selected_passphrase():
    if vault_tree is None or vault_passphrase_var is None:
        return
    sel = vault_tree.selection()
    if not sel:
        messagebox.showerror('Error', 'No key selected')
        return
    fp = sel[0]
    if backend.store_passphrase(fp, ''):
        vault_passphrase_var.set('')
        refresh_vault_view(fp)
        messagebox.showinfo('Cleared', 'Passphrase removed for selected key')

def save_public_key_from_widget(widget, store_slot=None):
    text = widget.get('1.0', tk.END).strip()
    if not text:
        messagebox.showerror('Error', 'No public key data present')
        return
    try:
        fp = backend.load_public_key_from_text(text)
    except Exception as exc:
        messagebox.showerror('Error', str(exc))
        return
    if store_slot:
        backend.store_key(store_slot, text)
    refresh_vault_view(fp)
    messagebox.showinfo('Saved', f'Public key stored with fingerprint {fp}')

def save_private_key_from_widget(widget, store_slot=None, passphrase_source=None):
    text = widget.get('1.0', tk.END).strip()
    if not text:
        messagebox.showerror('Error', 'No private key data present')
        return
    passphrase = passphrase_source.get() if passphrase_source is not None else None
    if passphrase_source is None:
        passphrase = sd.askstring('Passphrase', 'Enter private key passphrase (leave blank if none):', show='*')
    try:
        fp = backend.load_private_key_from_text(text, passphrase=passphrase or None, persist_bucket=store_slot)
    except Exception as exc:
        messagebox.showerror('Error', str(exc))
        return
    if passphrase_source is not None:
        passphrase_source.delete(0, tk.END)
        if passphrase:
            passphrase_source.insert(0, passphrase)
    refresh_vault_view(fp)
    messagebox.showinfo('Saved', f'Private key stored with fingerprint {fp}')

def load_vault_selection_into(var, widget, kind, passphrase_target=None):
    if vault_tree is None:
        return
    sel = vault_tree.selection()
    if not sel:
        messagebox.showerror('Error', 'No key selected')
        return
    fp = sel[0]
    if var is not None:
        var.set(fp)
    load_selected_key_into(widget, kind, var, passphrase_target)

def selected_vault_fp():
    if vault_tree is None:
        return None
    sel = vault_tree.selection()
    if not sel:
        messagebox.showerror('Error', 'No key selected')
        return None
    return sel[0]

def save_vault_private_from_text():
    fp = selected_vault_fp()
    if not fp or vault_private_text is None:
        return
    backend.persist_private_key(fp, vault_private_text.get('1.0', tk.END), vault_passphrase_var.get())
    refresh_vault_view(fp)
    messagebox.showinfo('Saved', 'Private key updated from editor')

def save_vault_public_from_text():
    fp = selected_vault_fp()
    if not fp or vault_public_text is None:
        return
    backend.persist_public_key(fp, vault_public_text.get('1.0', tk.END))
    refresh_vault_view(fp)
    messagebox.showinfo('Saved', 'Public key updated from editor')

def export_selected_vault(kind):
    fp = selected_vault_fp()
    if not fp:
        return
    export_vault_key(fp, kind)

class FirebaseLoginWindow:
    """Dedicated login window for Firebase authentication and master password setup."""

    def __init__(self):
        self.root = tk.Tk()
        self.root.title('Sign in to Encryption Vault')
        self.root.resizable(False, False)
        self.root.protocol('WM_DELETE_WINDOW', self._on_close)

        self.result = False
        self.cancelled = False
        self._stage = 'auth'
        self._pending_verification_session = None
        self._pending_credentials: Optional[tuple[str, str]] = None

        self.status_var = tk.StringVar(value='Sign in or register with email and password to continue.')

        self.auth_frame = ttk.Frame(self.root, padding=20)
        self.auth_frame.pack(fill='both', expand=True)
        self.auth_frame.columnconfigure(1, weight=1)

        ttk.Label(
            self.auth_frame,
            text='Sign in to Encryption Vault',
            font=('TkDefaultFont', 13, 'bold')
        ).grid(row=0, column=0, columnspan=2, sticky='w')

        ttk.Label(self.auth_frame, text='Email').grid(row=1, column=0, sticky='w', pady=(12, 0))
        self.email_var = tk.StringVar()
        self.email_entry = ttk.Entry(self.auth_frame, textvariable=self.email_var, width=32)
        self.email_entry.grid(row=1, column=1, sticky='ew', pady=(12, 0))

        ttk.Label(self.auth_frame, text='Password').grid(row=2, column=0, sticky='w', pady=(8, 0))
        self.password_var = tk.StringVar()
        self.password_entry = ttk.Entry(self.auth_frame, textvariable=self.password_var, show='*', width=32)
        self.password_entry.grid(row=2, column=1, sticky='ew', pady=(8, 0))

        button_row = ttk.Frame(self.auth_frame)
        button_row.grid(row=3, column=0, columnspan=2, sticky='ew', pady=(12, 0))
        button_row.columnconfigure(0, weight=1)
        button_row.columnconfigure(1, weight=1)
        self.email_button = ttk.Button(button_row, text='Sign In', command=self._handle_email_login)
        self.email_button.grid(row=0, column=0, sticky='ew')
        self.register_button = ttk.Button(button_row, text='Register Account', command=self._handle_register)
        self.register_button.grid(row=0, column=1, sticky='ew', padx=(8, 0))

        self.check_button = ttk.Button(self.auth_frame, text='I have verified - Check status', command=self._check_verification_status, state='disabled')
        self.check_button.grid(row=4, column=0, columnspan=2, sticky='ew', pady=(10, 0))

        self.status_label = ttk.Label(self.auth_frame, textvariable=self.status_var, wraplength=360, foreground='#444')
        self.status_label.grid(row=5, column=0, columnspan=2, sticky='w', pady=(12, 0))

        self.resend_button = ttk.Button(self.auth_frame, text='Resend Verification Email', command=self._resend_verification)
        self.resend_button.grid(row=6, column=0, columnspan=2, sticky='w', pady=(10, 0))
        self.resend_button.configure(state='disabled')

        button_frame = ttk.Frame(self.auth_frame)
        button_frame.grid(row=7, column=0, columnspan=2, sticky='ew', pady=(18, 0))
        button_frame.columnconfigure(0, weight=1)
        ttk.Button(button_frame, text='Cancel', command=self._on_close).grid(row=0, column=0, sticky='e')

        self.email_entry.focus_set()
        self.password_entry.bind('<Return>', lambda _: self._handle_email_login())

    def _set_status(self, message: str, *, error: bool = False) -> None:
        self.status_var.set(message)
        self.status_label.configure(foreground='#a00' if error else '#444')

    def _set_auth_enabled(self, enabled: bool) -> None:
        state = 'normal' if enabled else 'disabled'
        for widget in (self.email_entry, self.password_entry, self.email_button, self.register_button):
            widget.configure(state=state)

    def _start_auth_thread(self, action):
        def runner():
            try:
                action()
            except EmailNotVerifiedError as exc:
                self.root.after(0, lambda: self._handle_unverified(exc))
            except FirebaseAuthError as exc:
                self.root.after(0, lambda: self._auth_failed(str(exc)))
            except Exception as exc:  # pragma: no cover - surface unexpected issues
                self.root.after(0, lambda: self._auth_failed(str(exc)))
            else:
                self.root.after(0, self._on_auth_success)
        threading.Thread(target=runner, daemon=True).start()

    def _handle_email_login(self) -> None:
        email = self.email_var.get().strip()
        password = self.password_var.get()
        if not email or not password:
            self._set_status('Email and password are required.', error=True)
            return
        self._pending_credentials = None
        self._pending_verification_session = None
        self.resend_button.configure(state='disabled')
        self.check_button.configure(state='disabled')
        self._set_status('Signing in...')
        self._set_auth_enabled(False)
        self._start_auth_thread(lambda: backend.authenticate_with_email(email, password))

    def _handle_register(self) -> None:
        email = self.email_var.get().strip()
        password = self.password_var.get()
        if not email or not password:
            self._set_status('Email and password are required to register.', error=True)
            return
        if len(password) < 6:
            self._set_status('Password must be at least 6 characters long.', error=True)
            return
        confirm = sd.askstring('Confirm Password', 'Re-enter password:', show='*')
        if confirm is None:
            return
        if password != confirm:
            self._set_status('Passwords do not match. Please try again.', error=True)
            return
        self._pending_credentials = (email, password)
        self._pending_verification_session = None
        self.resend_button.configure(state='disabled')
        self.check_button.configure(state='disabled')
        self._set_status('Creating account...')
        self._set_auth_enabled(False)
        self._start_auth_thread(lambda: backend.register_account(email, password))

    def _handle_unverified(self, error: EmailNotVerifiedError) -> None:
        self._pending_verification_session = error.session
        message = str(error)
        success = message.lower().startswith('account created')
        next_steps = (
            f'{message}\n\nOnce verified, click "I have verified - Check status" to continue.'
            if success else message
        )
        self._set_status(next_steps, error=not success)
        self.resend_button.configure(state='normal')
        if self._pending_credentials:
            self.check_button.configure(state='normal')
        self._set_auth_enabled(True)

    def _auth_failed(self, message: str | None) -> None:
        if not message:
            message = 'Authentication failed. Please try again.'
        parsed_msg: str = message
        try:
            data = json.loads(message)
            err = data.get('error', {})
            parsed = err.get('message')
            if isinstance(parsed, str) and parsed:
                parsed_msg = parsed
            elif parsed:
                parsed_msg = str(parsed)
            else:
                parsed_msg = 'Authentication failed. Please try again.'
        except (json.JSONDecodeError, TypeError, AttributeError):
            parsed_msg = parsed_msg or 'Authentication failed. Please try again.'

        if isinstance(parsed_msg, str):
            if 'INVALID_LOGIN_CREDENTIALS' in parsed_msg:
                parsed_msg = 'Incorrect email or password. Please try again.'
            elif 'EMAIL_EXISTS' in parsed_msg:
                parsed_msg = 'An account with this email already exists. Try signing in instead.'
            elif 'WEAK_PASSWORD' in parsed_msg:
                parsed_msg = 'Password should be at least 6 characters. Please choose a stronger password.'
            elif 'TOO_MANY_ATTEMPTS_TRY_LATER' in parsed_msg:
                parsed_msg = 'Too many attempts. Please wait a moment before trying again.'
        self._set_status(str(parsed_msg), error=True)
        self._set_auth_enabled(True)
        self.check_button.configure(state='disabled')

    def _resend_verification(self) -> None:
        session = self._pending_verification_session
        if not session:
            return
        self._set_status('Sending verification email...')
        self.resend_button.configure(state='disabled')
        self._set_auth_enabled(False)

        def runner():
            try:
                backend.resend_verification_email(session)
            except FirebaseAuthError as exc:
                self.root.after(0, lambda: self._on_resend_complete(str(exc), success=False))
            except Exception as exc:
                self.root.after(0, lambda: self._on_resend_complete(str(exc), success=False))
            else:
                self.root.after(0, lambda: self._on_resend_complete('Verification email sent. Please check your inbox before signing in.', success=True))

        threading.Thread(target=runner, daemon=True).start()

    def _on_resend_complete(self, message: str, *, success: bool) -> None:
        self._set_status(message, error=not success)
        self._set_auth_enabled(True)
        if success:
            self.resend_button.configure(state='disabled')
        else:
            self.resend_button.configure(state='normal')

    def _check_verification_status(self) -> None:
        creds = self._pending_credentials
        if not creds:
            self._set_status('No pending registration. Please register or sign in first.', error=True)
            return
        email, password = creds
        self._set_status('Checking verification status...')
        self._set_auth_enabled(False)
        self.check_button.configure(state='disabled')
        self._start_auth_thread(lambda: backend.authenticate_with_email(email, password))

    def _on_auth_success(self) -> None:
        self._pending_verification_session = None
        self._pending_credentials = None
        self._set_status('Authentication successful.')
        self._set_auth_enabled(True)
        self.resend_button.configure(state='disabled')
        self.check_button.configure(state='disabled')
        self._show_master_password_stage()

    def _show_master_password_stage(self) -> None:
        if self._stage == 'master':
            return
        self._stage = 'master'
        self.auth_frame.pack_forget()

        self.master_frame = ttk.Frame(self.root, padding=20)
        self.master_frame.pack(fill='both', expand=True)
        self.master_frame.columnconfigure(0, weight=1)
        self.master_frame.columnconfigure(1, weight=1)

        ttk.Label(
            self.master_frame,
            text='Unlock Vault',
            font=('TkDefaultFont', 13, 'bold')
        ).grid(row=0, column=0, columnspan=2, sticky='w')

        self.master_status_var = tk.StringVar(value='')
        self.master_status_label = ttk.Label(self.master_frame, textvariable=self.master_status_var, wraplength=360, foreground='#a00')

        if backend.has_master_password():
            ttk.Label(
                self.master_frame,
                text='Enter your master password to unlock stored secrets.'
            ).grid(row=1, column=0, columnspan=2, sticky='w', pady=(12, 0))
            self.master_password_var = tk.StringVar()
            self.master_entry = ttk.Entry(self.master_frame, textvariable=self.master_password_var, show='*', width=32)
            self.master_entry.grid(row=2, column=0, columnspan=2, sticky='ew', pady=(8, 0))
            ttk.Button(self.master_frame, text='Unlock', command=self._verify_master_password).grid(row=3, column=0, columnspan=2, sticky='ew', pady=(12, 0))
            self.master_status_label.grid(row=4, column=0, columnspan=2, sticky='w', pady=(8, 0))
            ttk.Button(self.master_frame, text='Cancel', command=self._on_close).grid(row=5, column=0, columnspan=2, sticky='e', pady=(18, 0))
            self.master_entry.focus_set()
            self.master_entry.bind('<Return>', lambda _: self._verify_master_password())
        else:
            ttk.Label(
                self.master_frame,
                text='Create a master password to encrypt your vault.'
            ).grid(row=1, column=0, columnspan=2, sticky='w', pady=(12, 0))
            self.new_master_var = tk.StringVar()
            self.confirm_master_var = tk.StringVar()
            ttk.Label(self.master_frame, text='Master password').grid(row=2, column=0, sticky='w', pady=(8, 0))
            self.new_master_entry = ttk.Entry(self.master_frame, textvariable=self.new_master_var, show='*', width=32)
            self.new_master_entry.grid(row=2, column=1, sticky='ew', pady=(8, 0))
            ttk.Label(self.master_frame, text='Confirm password').grid(row=3, column=0, sticky='w', pady=(8, 0))
            self.confirm_master_entry = ttk.Entry(self.master_frame, textvariable=self.confirm_master_var, show='*', width=32)
            self.confirm_master_entry.grid(row=3, column=1, sticky='ew', pady=(8, 0))
            ttk.Button(self.master_frame, text='Save Master Password', command=self._set_new_master_password).grid(row=4, column=0, columnspan=2, sticky='ew', pady=(12, 0))
            self.master_status_label.grid(row=5, column=0, columnspan=2, sticky='w', pady=(8, 0))
            ttk.Button(self.master_frame, text='Cancel', command=self._on_close).grid(row=6, column=0, columnspan=2, sticky='e', pady=(18, 0))
            self.new_master_entry.focus_set()
            self.confirm_master_entry.bind('<Return>', lambda _: self._set_new_master_password())

    def _set_master_status(self, message: str, *, error: bool = True) -> None:
        self.master_status_var.set(message)
        self.master_status_label.configure(foreground='#0a7' if not error else '#a00')

    def _verify_master_password(self) -> None:
        pw = self.master_password_var.get()
        if not pw:
            self._set_master_status('Master password is required.')
            return
        if backend.verify_master_password(pw):
            self._set_master_status('Vault unlocked.', error=False)
            self.result = True
            self.root.after(100, self.root.destroy)
        else:
            self._set_master_status('Invalid master password. Try again.')

    def _set_new_master_password(self) -> None:
        pw = self.new_master_var.get()
        confirm = self.confirm_master_var.get()
        if not pw:
            self._set_master_status('Master password cannot be empty.')
            return
        if pw != confirm:
            self._set_master_status('Passwords do not match.')
            return
        try:
            backend.initialise_master_password(pw)
        except Exception as exc:
            self._set_master_status(str(exc))
            return
        self._set_master_status('Master password saved.', error=False)
        self.result = True
        self.root.after(100, self.root.destroy)

    def _on_close(self) -> None:
        self.cancelled = True
        self.result = False
        try:
            self.root.destroy()
        except Exception:
            pass

    def show(self) -> bool:
        self.root.mainloop()
        return self.result and not self.cancelled


def login_flow() -> bool:
    dialog = FirebaseLoginWindow()
    return dialog.show()

def generate_key_action():
    n = sd.askstring('Name', 'Enter name:')
    e = sd.askstring('Email', 'Enter email:')
    if not n or e is None:
        return
    pp=sd.askstring('Passphrase','Set passphrase (optional):',show='*')
    def wk():
        try:
            pr,pb,fp=generate_keypair(n,e,pp or None)
        except Exception as exc:
            root.after(0, lambda msg=str(exc): messagebox.showerror('Error', msg))
            return
        def apply():
            backend.persist_private_key(fp, pr, passphrase=pp or None)
            backend.persist_public_key(fp, pb)
            refresh_vault_view(fp)
            messagebox.showinfo('Generated',f'Keypair generated with fingerprint {fp} and stored in vault')
        root.after(0, apply)
    threading.Thread(target=wk,daemon=True).start()

def encrypt_action():
    k=load_public_key_from_text(public_key_text.get('1.0',tk.END).strip())
    ct=encrypt_message(k,plaintext_text.get('1.0',tk.END))
    ciphertext_text.delete('1.0',tk.END)
    ciphertext_text.insert(tk.END,ct)

def decrypt_action():
    kt=private_key_text.get('1.0',tk.END).strip()
    ct=ciphertext_input.get('1.0',tk.END).strip()
    pw=passphrase_entry.get()
    def wk():
        try:
            priv_fp=load_private_key_from_text(kt, passphrase=pw or None)
            pt=decrypt_message(priv_fp,ct,pw)
            root.after(0,lambda:(decrypted_text.delete('1.0',tk.END),decrypted_text.insert(tk.END,pt)))
        except Exception as e:
            err_msg = str(e)
            root.after(0,lambda msg=err_msg: messagebox.showerror('Error',msg))
    threading.Thread(target=wk,daemon=True).start()

def sign_action():
    k=load_private_key_from_text(private_sign_key_text.get('1.0',tk.END).strip(), passphrase=passphrase_sign_entry.get() or None)
    msg=sign_input_text.get('1.0',tk.END).rstrip('\n')
    sig=sign_message_detached(k,msg,passphrase_sign_entry.get())
    signature_text.delete('1.0',tk.END)
    signature_text.insert(tk.END,'-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\n'+msg+'\n'+sig)

def sign_file_action():
    p=filedialog.askopenfilename(title='Select file to sign')
    if not p:
        return
    k=load_private_key_from_text(private_sign_key_text.get('1.0',tk.END).strip(), passphrase=passphrase_sign_entry.get() or None)
    sig=sign_message_detached(k,open(p).read(),passphrase_sign_entry.get())
    open(p+'.sig','w').write(sig)
    messagebox.showinfo('Signed',f'Signature saved to {p}.sig')

def verify_action():
    pub=load_public_key_from_text(public_verify_key_text.get('1.0',tk.END).strip())
    sm=verify_input_text.get('1.0',tk.END).strip()
    if not sm:
        messagebox.showerror('Error','No signed message')
        return
    try:
        v,orig=verify_signature(pub,sm)
    except TypeError:
        fp=filedialog.askopenfilename(title='Select original message file')
        if not fp:
            return
        orig=open(fp).read()
        s= gpg.verify(sm)
        v=bool(s.valid)
    verify_output_text.delete('1.0',tk.END)
    verify_output_text.insert(tk.END,f'Valid: {v}\n\n{orig}')

def copy_to_clipboard(w):
    root.clipboard_clear()
    root.clipboard_append(w.get('1.0',tk.END).strip())

def save_aes_ciphertext_action():
    ct=aes_ciphertext_text.get('1.0',tk.END).strip()
    if not ct:
        messagebox.showerror('Error','No ciphertext')
        return
    path=os.path.join(os.path.expanduser('~/Downloads'),'aes256_ciphertext.txt')
    open(path,'w').write(ct)
    messagebox.showinfo('Saved',f'Ciphertext saved to {path}')

def export_public_key(k):
    t=get_stored_key(k)
    if not t:
        messagebox.showerror('Error','No key stored')
        return
    path=filedialog.asksaveasfilename(defaultextension='.asc',filetypes=[('PGP Public Key','*.asc')])
    if path:
        open(path,'w').write(t)
        r = gpg.import_keys(t)
        if r.fingerprints:
            open(path+'.fingerprint','w').write(r.fingerprints[-1])

def export_private_key(k):
    if not verify_master_password(sd.askstring('Password','Enter master password:',show='*')):
        messagebox.showerror('Error','Invalid password')
        return
    t=get_stored_key(k)
    path=filedialog.asksaveasfilename(defaultextension='.asc',filetypes=[('PGP Private Key','*.asc')])
    if path:
        open(path,'w').write(t)
        r = gpg.import_keys(t)
        if r.fingerprints:
            open(path+'.fingerprint','w').write(r.fingerprints[-1])

def save_encrypted_action():
    ct=ciphertext_text.get('1.0',tk.END).strip()
    if not ct:
        messagebox.showerror('Error','No ciphertext')
        return
    path=filedialog.asksaveasfilename(defaultextension='.pgp',filetypes=[('PGP Files','*.pgp')])
    if path:
        open(path,'w').write(ct)
        messagebox.showinfo('Saved',f'Encrypted message saved to {path}')

def save_decrypted_action():
    pt=decrypted_text.get('1.0',tk.END)
    if not pt.strip():
        messagebox.showerror('Error','No decrypted text')
        return
    path=filedialog.asksaveasfilename(defaultextension='.txt',filetypes=[('Text Files','*.txt')])
    if path:
        open(path,'w').write(pt)
        messagebox.showinfo('Saved',f'Decrypted message saved to {path}')

def _active_aes_key():
    key = backend.aes_secret_key or backend.master_password
    if not key:
        raise ValueError('No AES key available. Unlock an account or set a unique AES key.')
    return key

def set_unique_aes():
    k=sd.askstring('AES Key','Enter unique AES key\n(leave blank to use master password):',show='*')
    backend.aes_secret_key=k or None
    aes_mode_label.config(text=f'AES mode: {"Unique Key" if k else "Master Password"}')

def use_master_aes():
    backend.aes_secret_key=None
    aes_mode_label.config(text='AES mode: Master Password')

def aes_encrypt_action():
    try:
        key = _active_aes_key()
    except ValueError as exc:
        messagebox.showerror('Error', str(exc))
        return
    ct=aes_encrypt(key,aes_plaintext_text.get('1.0',tk.END))
    aes_ciphertext_text.delete('1.0',tk.END)
    aes_ciphertext_text.insert(tk.END,ct)

def aes_decrypt_action():
    try:
        key = _active_aes_key()
        pt=aes_decrypt(key,aes_ciphertext_text.get('1.0',tk.END).strip())
        aes_decrypted_text.delete('1.0',tk.END)
        aes_decrypted_text.insert(tk.END,pt)
    except Exception as e:
        messagebox.showerror('Error',str(e))

def aes_encrypt_file_action():
    p=filedialog.askopenfilename(title='Select file to encrypt')
    if not p:
        return
    data=open(p,'rb').read()
    iv=os.urandom(16)
    try:
        key = _active_aes_key()
    except ValueError as exc:
        messagebox.showerror('Error', str(exc))
        return
    enc=Cipher(
        algorithms.AES(derive_aes_key(key)),
        modes.CBC(iv),
        backend=default_backend()
    ).encryptor()
    pad=padding.PKCS7(128).padder()
    ct=enc.update(pad.update(data)+pad.finalize())+enc.finalize()
    op=p+'.aes256'
    open(op,'wb').write(iv+ct)
    messagebox.showinfo('Encrypted',f'File encrypted to {op}')

def aes_decrypt_file_action():
    p=filedialog.askopenfilename(title='Select encrypted file to decrypt',filetypes=[('Encrypted Files','*.aes256 *.enc')])
    if not p:
        return
    raw=open(p,'rb').read()
    iv,ct=raw[:16],raw[16:]
    try:
        key = _active_aes_key()
    except ValueError as exc:
        messagebox.showerror('Error', str(exc))
        return
    dec=Cipher(
        algorithms.AES(derive_aes_key(key)),
        modes.CBC(iv),
        backend=default_backend()
    ).decryptor()
    padded=dec.update(ct)+dec.finalize()
    unpadder=padding.PKCS7(128).unpadder()
    data=unpadder.update(padded)+unpadder.finalize()
    base,ext=os.path.splitext(p)
    out=base if ext.lower() in ('.aes256','.enc') else p+'.dec'
    open(out,'wb').write(data)
    messagebox.showinfo('Decrypted',f'File decrypted to {out}')

def generate_random_password(length=20, style='apple', **options):
    """Backwards-compatible wrapper around the enhanced password generator."""
    return generate_password(length=length, style=style, **options)

def add_cred():
    style = sd.askstring(
        'Password Style',
        'Choose password style:\n'
        '- apple (easy to type, default)\n'
        '- memorable (word-based passphrase)\n'
        '- random (full character set)',
        initialvalue='apple'
    )
    if style is None:
        return
    style_norm = (style.strip().lower() or 'apple')
    options = {}
    if style_norm in {'apple', 'friendly', 'default'}:
        segments = sd.askinteger(
            'Segments',
            'Number of segments (e.g. 4 -> xxxx-xxxx-xxxx-xxxx):',
            initialvalue=4,
            minvalue=2,
            maxvalue=6
        )
        if segments is None:
            return
        segment_length = sd.askinteger(
            'Segment Length',
            'Characters per segment:',
            initialvalue=4,
            minvalue=3,
            maxvalue=8
        )
        if segment_length is None:
            return
        options.update(segments=segments, segment_length=segment_length)
        style_norm = 'apple'
    elif style_norm in {'memorable', 'words', 'passphrase'}:
        num_words = sd.askinteger(
            'Word Count',
            'Number of words to use:',
            initialvalue=4,
            minvalue=2,
            maxvalue=8
        )
        if num_words is None:
            return
        suffix_digits = sd.askinteger(
            'Digits',
            'Digits to append (0 for none):',
            initialvalue=2,
            minvalue=0,
            maxvalue=4
        )
        if suffix_digits is None:
            return
        options.update(num_words=num_words, suffix_digits=suffix_digits)
        style_norm = 'memorable'
    else:
        length = sd.askinteger(
            'Password Length',
            'Enter length for generated password:',
            initialvalue=20,
            minvalue=8,
            maxvalue=128
        )
        if length is None:
            return
        options['length'] = length
        style_norm = 'random'

    pw = generate_random_password(style=style_norm, **options)
    root.clipboard_clear()
    root.clipboard_append(pw)
    messagebox.showinfo(
        'Generated Password',
        f'A new password has been generated and copied to clipboard:\n{pw}'
    )
    site = sd.askstring('Site', 'Enter site:')
    usr = sd.askstring('Username', 'Enter username:')
    if not site or usr is None:
        return
    c = load_credentials()
    c[site] = {'username': usr, 'password': pw}
    save_credentials(c)
    refresh_credentials_view()

def edit_cred():
    if not tree.selection():
        return
    site=tree.selection()[0]
    info=load_credentials().get(site,{})
    usr=sd.askstring('Username','Edit username:',initialvalue=info.get('username',''))
    if usr is None:
        return
    pw=sd.askstring('Password','Edit password:',show='*',initialvalue=info.get('password',''))
    if pw is None:
        return
    c=load_credentials()
    c[site]={'username':usr,'password':pw}
    save_credentials(c)
    refresh_credentials_view()

def delete_cred():
    if not tree.selection():
        return
    c=load_credentials()
    for s in tree.selection():
        c.pop(s,None)
    save_credentials(c)
    refresh_credentials_view()

def show_password():
    if not tree.selection():
        return
    site=tree.selection()[0]
    messagebox.showinfo('Password',f"{site}: {load_credentials().get(site,{}).get('password','')}")

def copy_selected(f):
    if not tree.selection():
        return
    val=load_credentials().get(tree.selection()[0],{}).get(f,'')
    root.clipboard_clear()
    root.clipboard_append(val)

def add_card():
    h=sd.askstring('Cardholder','Enter cardholder name:')
    n=sd.askstring('Card Number','Enter card number (digits only):')
    if h is None or n is None or not valid_card(n):
        messagebox.showerror('Error','Invalid input')
        return
    exp=parse_expiration(sd.askstring('Expiry','Enter expiry date (MM/YY, M/YY, MMYY, MYY):'))
    if not exp:
        messagebox.showerror('Error','Invalid expiry')
        return
    cvv=sd.askstring('CVV','Enter 3-digit CVV (optional):',show='*')
    if cvv and not re.fullmatch(r'\d{3}',cvv):
        messagebox.showerror('Error','Invalid CVV')
        return
    c=load_cards()
    c[n]={'holder':h,'expires':exp,'cvv':cvv or ''}
    save_cards(c)
    refresh_cards_view()

def edit_card():
    if not card_tree.selection():
        return
    num=card_tree.selection()[0]
    info=load_cards().get(num,{})
    h=sd.askstring('Cardholder','Edit cardholder:',initialvalue=info.get('holder',''))
    exp=parse_expiration(sd.askstring('Expiry','Edit expiry (MM/YY):',initialvalue=info.get('expires','')))
    cvv=sd.askstring('CVV','Edit CVV:',show='*',initialvalue=info.get('cvv',''))
    if h is None or exp is None or (cvv and not re.fullmatch(r'\d{3}',cvv)):
        return
    c=load_cards()
    c[num]={'holder':h,'expires':exp,'cvv':cvv or ''}
    save_cards(c)
    refresh_cards_view()

def delete_card():
    c=load_cards()
    for s in card_tree.selection():
        c.pop(s,None)
    save_cards(c)
    refresh_cards_view()

def show_card():
    if not card_tree.selection():
        return
    n=card_tree.selection()[0]
    i=load_cards().get(n,{})
    messagebox.showinfo('Card',f"{i.get('holder','')} {n} exp {i.get('expires','')} CVV {i.get('cvv','')}")

def copy_card_number():
    if card_tree.selection():
        root.clipboard_clear()
        root.clipboard_append(card_tree.selection()[0])

def copy_exp_month():
    if card_tree.selection():
        exp=load_cards().get(card_tree.selection()[0],{}).get('expires','')
        root.clipboard_clear()
        root.clipboard_append(exp.split('/')[0] if exp else '')

def copy_exp_year():
    if card_tree.selection():
        exp=load_cards().get(card_tree.selection()[0],{}).get('expires','')
        root.clipboard_clear()
        root.clipboard_append(exp.split('/')[1] if exp and '/' in exp else '')

def copy_cvv():
    if card_tree.selection():
        cvv=load_cards().get(card_tree.selection()[0],{}).get('cvv','')
        root.clipboard_clear()
        root.clipboard_append(cvv)

if not login_flow():
    sys.exit()

root=tk.Tk()
title_suffix = f' - {backend.ACCOUNT_ID}' if backend.ACCOUNT_ID else ''
root.title(f'Encryption & Credentials by Bo Shang{title_suffix}')

menubar=tk.Menu(root)

keys_menu=tk.Menu(menubar,tearoff=0)
keys_menu.add_command(label='Import Encryption Public Key',command=lambda: import_public_key_from_file('public_key',public_key_text))
keys_menu.add_command(label='Import Encryption Private Key',command=lambda: import_private_key_from_file('private_key',private_key_text,passphrase_entry))
keys_menu.add_command(label='Import Signing Private Key',command=lambda: import_private_key_from_file('sign_private_key',private_sign_key_text,passphrase_sign_entry))
keys_menu.add_command(label='Import Verify Public Key',command=lambda: import_public_key_from_file('verify_public_key',public_verify_key_text))
keys_menu.add_separator()
keys_menu.add_command(label='Generate Keypair',command=generate_key_action)
keys_menu.add_command(label='Show PGP Vault',command=lambda: nb.select(vault_frame))
menubar.add_cascade(label='Keys',menu=keys_menu)
root.config(menu=menubar)

nb=ttk.Notebook(root)
vault_frame=ttk.Frame(nb)
encrypt_frame=ttk.Frame(nb)
decrypt_frame=ttk.Frame(nb)
sign_frame=ttk.Frame(nb)
verify_frame=ttk.Frame(nb)
aes_frame=ttk.Frame(nb)

nb.add(vault_frame,text='PGP Vault')
nb.add(encrypt_frame,text='Encrypt')
nb.add(decrypt_frame,text='Decrypt')
nb.add(sign_frame,text='Sign')
nb.add(verify_frame,text='Verify')
nb.add(aes_frame,text='AES256')
nb.pack(expand=1,fill='both')
nb.select(encrypt_frame)

pub_frame=ttk.LabelFrame(encrypt_frame,text='Public Key')
pub_frame.pack(fill='x',padx=5,pady=5)

public_key_text=scrolledtext.ScrolledText(pub_frame,height=10)
public_key_var=tk.StringVar()
public_sel_frame=ttk.Frame(pub_frame)
public_sel_frame.pack(fill='x',padx=5,pady=5)
public_key_combo=ttk.Combobox(public_sel_frame,textvariable=public_key_var,state='readonly')
public_key_combo.pack(side='left',fill='x',expand=True,padx=5)
ttk.Button(public_sel_frame,text='Load Selected',command=lambda: load_selected_key_into(public_key_text,'public',public_key_var)).pack(side='left',padx=5)
ttk.Button(public_sel_frame,text='Export Selected',command=lambda: export_selected_from_combo('public',public_key_var)).pack(side='left',padx=5)
public_key_text.pack(fill='x',padx=5,pady=5)
if (s:=get_stored_key('public_key')):
    public_key_text.insert('1.0',s)
public_btn_frame=ttk.Frame(pub_frame)
public_btn_frame.pack(fill='x',padx=5,pady=5)
ttk.Button(public_btn_frame,text='Import From File',command=lambda: import_public_key_from_file('public_key',public_key_text)).pack(side='left',padx=5)
ttk.Button(public_btn_frame,text='Save From Text',command=lambda: save_public_key_from_widget(public_key_text,'public_key')).pack(side='left',padx=5)

pt_frame=ttk.LabelFrame(encrypt_frame,text='Plaintext')
pt_frame.pack(fill='both',expand=True,padx=5,pady=5)
plaintext_text=scrolledtext.ScrolledText(pt_frame,height=10)
plaintext_text.pack(fill='both',expand=True,padx=5,pady=5)

ttk.Button(encrypt_frame,text='Encrypt',command=encrypt_action).pack(padx=5,pady=5)

ct_frame=ttk.LabelFrame(encrypt_frame,text='Ciphertext')
ct_frame.pack(fill='both',expand=True,padx=5,pady=5)
ciphertext_text=scrolledtext.ScrolledText(ct_frame,height=10)
ciphertext_text.pack(fill='both',expand=True,padx=5,pady=5)
for l,c in (
    ('Copy Encrypted',lambda: copy_to_clipboard(ciphertext_text)),
    ('Save Encrypted',save_encrypted_action)
):
    ttk.Button(encrypt_frame,text=l,command=c).pack(padx=5,pady=5)

priv_frame=ttk.LabelFrame(decrypt_frame,text='Private Key')
priv_frame.pack(fill='x',padx=5,pady=5)
private_key_text=scrolledtext.ScrolledText(priv_frame,height=10)
private_key_var=tk.StringVar()
private_sel_frame=ttk.Frame(priv_frame)
private_sel_frame.pack(fill='x',padx=5,pady=5)
private_key_combo=ttk.Combobox(private_sel_frame,textvariable=private_key_var,state='readonly')
private_key_combo.pack(side='left',fill='x',expand=True,padx=5)
ttk.Button(private_sel_frame,text='Load Selected',command=lambda: load_selected_key_into(private_key_text,'private',private_key_var,passphrase_entry)).pack(side='left',padx=5)
ttk.Button(private_sel_frame,text='Export Selected',command=lambda: export_selected_from_combo('private',private_key_var)).pack(side='left',padx=5)
private_key_text.pack(fill='x',padx=5,pady=5)
if (s:=get_stored_key('private_key')):
    private_key_text.insert('1.0',s)
private_btn_frame=ttk.Frame(priv_frame)
private_btn_frame.pack(fill='x',padx=5,pady=5)
ttk.Button(private_btn_frame,text='Import From File',command=lambda: import_private_key_from_file('private_key',private_key_text,passphrase_entry)).pack(side='left',padx=5)
ttk.Button(private_btn_frame,text='Save From Text',command=lambda: save_private_key_from_widget(private_key_text,'private_key',passphrase_entry)).pack(side='left',padx=5)

pp_frame=ttk.LabelFrame(decrypt_frame,text='Passphrase')
pp_frame.pack(fill='x',padx=5,pady=5)
passphrase_entry=ttk.Entry(pp_frame,show='*')
passphrase_entry.pack(fill='x',padx=5,pady=5)

dec_in_frame=ttk.LabelFrame(decrypt_frame,text='Ciphertext')
dec_in_frame.pack(fill='both',expand=True,padx=5,pady=5)
ciphertext_input=scrolledtext.ScrolledText(dec_in_frame,height=10)
ciphertext_input.pack(fill='both',expand=True,padx=5,pady=5)

ttk.Button(decrypt_frame,text='Decrypt',command=decrypt_action).pack(padx=5,pady=5)

dec_out_frame=ttk.LabelFrame(decrypt_frame,text='Plaintext')
dec_out_frame.pack(fill='both',expand=True,padx=5,pady=5)
decrypted_text=scrolledtext.ScrolledText(dec_out_frame,height=10)
decrypted_text.pack(fill='both',expand=True,padx=5,pady=5)
for l,c in (
    ('Copy Decrypted',lambda: copy_to_clipboard(decrypted_text)),
    ('Save Decrypted',save_decrypted_action)
):
    ttk.Button(decrypt_frame,text=l,command=c).pack(padx=5,pady=5)

spriv_frame=ttk.LabelFrame(sign_frame,text='Private Key')
spriv_frame.pack(fill='x',padx=5,pady=5)
private_sign_key_text=scrolledtext.ScrolledText(spriv_frame,height=10)
sign_key_var=tk.StringVar()
sign_sel_frame=ttk.Frame(spriv_frame)
sign_sel_frame.pack(fill='x',padx=5,pady=5)
sign_key_combo=ttk.Combobox(sign_sel_frame,textvariable=sign_key_var,state='readonly')
sign_key_combo.pack(side='left',fill='x',expand=True,padx=5)
ttk.Button(sign_sel_frame,text='Load Selected',command=lambda: load_selected_key_into(private_sign_key_text,'private',sign_key_var,passphrase_sign_entry)).pack(side='left',padx=5)
ttk.Button(sign_sel_frame,text='Export Selected',command=lambda: export_selected_from_combo('private',sign_key_var)).pack(side='left',padx=5)
private_sign_key_text.pack(fill='x',padx=5,pady=5)
if (s:=get_stored_key('sign_private_key')):
    private_sign_key_text.insert('1.0',s)
sign_btn_frame=ttk.Frame(spriv_frame)
sign_btn_frame.pack(fill='x',padx=5,pady=5)
ttk.Button(sign_btn_frame,text='Import From File',command=lambda: import_private_key_from_file('sign_private_key',private_sign_key_text,passphrase_sign_entry)).pack(side='left',padx=5)
ttk.Button(sign_btn_frame,text='Save From Text',command=lambda: save_private_key_from_widget(private_sign_key_text,'sign_private_key',passphrase_sign_entry)).pack(side='left',padx=5)

spp_frame=ttk.LabelFrame(sign_frame,text='Passphrase')
spp_frame.pack(fill='x',padx=5,pady=5)
passphrase_sign_entry=ttk.Entry(spp_frame,show='*')
passphrase_sign_entry.pack(fill='x',padx=5,pady=5)

sin_frame=ttk.LabelFrame(sign_frame,text='Message')
sin_frame.pack(fill='both',expand=True,padx=5,pady=5)
sign_input_text=scrolledtext.ScrolledText(sin_frame,height=10)
sign_input_text.pack(fill='both',expand=True,padx=5,pady=5)

for l,c in (('Sign',sign_action),('Sign File',sign_file_action)):
    ttk.Button(sign_frame,text=l,command=c).pack(side='left',padx=5,pady=5)

sout_frame=ttk.LabelFrame(sign_frame,text='Signed Message')
sout_frame.pack(fill='both',expand=True,padx=5,pady=5)
signature_text=scrolledtext.ScrolledText(sout_frame,height=10)
signature_text.pack(fill='both',expand=True,padx=5,pady=5)
ttk.Button(sign_frame,text='Copy Signature',command=lambda: copy_to_clipboard(signature_text)).pack(padx=5,pady=5)

vpub_frame=ttk.LabelFrame(verify_frame,text='Public Key')
vpub_frame.pack(fill='x',padx=5,pady=5)
public_verify_key_text=scrolledtext.ScrolledText(vpub_frame,height=10)
verify_key_var=tk.StringVar()
verify_sel_frame=ttk.Frame(vpub_frame)
verify_sel_frame.pack(fill='x',padx=5,pady=5)
verify_key_combo=ttk.Combobox(verify_sel_frame,textvariable=verify_key_var,state='readonly')
verify_key_combo.pack(side='left',fill='x',expand=True,padx=5)
ttk.Button(verify_sel_frame,text='Load Selected',command=lambda: load_selected_key_into(public_verify_key_text,'public',verify_key_var)).pack(side='left',padx=5)
ttk.Button(verify_sel_frame,text='Export Selected',command=lambda: export_selected_from_combo('public',verify_key_var)).pack(side='left',padx=5)
public_verify_key_text.pack(fill='x',padx=5,pady=5)
if (s:=get_stored_key('verify_public_key')):
    public_verify_key_text.insert('1.0',s)
verify_btn_frame=ttk.Frame(vpub_frame)
verify_btn_frame.pack(fill='x',padx=5,pady=5)
ttk.Button(verify_btn_frame,text='Import From File',command=lambda: import_public_key_from_file('verify_public_key',public_verify_key_text)).pack(side='left',padx=5)
ttk.Button(verify_btn_frame,text='Save From Text',command=lambda: save_public_key_from_widget(public_verify_key_text,'verify_public_key')).pack(side='left',padx=5)

vin_frame=ttk.LabelFrame(verify_frame,text='Signed Message')
vin_frame.pack(fill='both',expand=True,padx=5,pady=5)
verify_input_text=scrolledtext.ScrolledText(vin_frame,height=10)
verify_input_text.pack(fill='both',expand=True,padx=5,pady=5)

ttk.Button(verify_frame,text='Verify',command=verify_action).pack(padx=5,pady=5)

vout_frame=ttk.LabelFrame(verify_frame,text='Original Message')
vout_frame.pack(fill='both',expand=True,padx=5,pady=5)
verify_output_text=scrolledtext.ScrolledText(vout_frame,height=10)
verify_output_text.pack(fill='both',expand=True,padx=5,pady=5)

vault_container=ttk.Frame(vault_frame)
vault_container.pack(fill='both',expand=True,padx=5,pady=5)

vault_list_frame=ttk.Frame(vault_container)
vault_list_frame.pack(side='left',fill='y',padx=5,pady=5)
vault_tree=ttk.Treeview(vault_list_frame,columns=('fingerprint','public','private','passphrase'),show='headings',height=15)
for col,txt,width in (
    ('fingerprint','Fingerprint',220),
    ('public','Public',70),
    ('private','Private',70),
    ('passphrase','Passphrase',90),
):
    vault_tree.heading(col,text=txt)
    vault_tree.column(col,width=width,anchor='center' if col!='fingerprint' else 'w')
vault_tree.bind('<<TreeviewSelect>>',on_vault_select)
vault_tree.pack(side='left',fill='y',expand=False)
vault_scroll=ttk.Scrollbar(vault_list_frame,orient='vertical',command=vault_tree.yview)
vault_scroll.pack(side='right',fill='y')
vault_tree.configure(yscrollcommand=vault_scroll.set)

vault_btn_frame=ttk.Frame(vault_container)
vault_btn_frame.pack(side='left',fill='y',padx=5,pady=5)
ttk.Button(vault_btn_frame,text='Import Public',command=lambda: import_public_key_from_file()).pack(fill='x',pady=2)
ttk.Button(vault_btn_frame,text='Import Private',command=lambda: import_private_key_from_file()).pack(fill='x',pady=2)
ttk.Button(vault_btn_frame,text='Delete Selected',command=lambda: delete_vault_entries(vault_tree.selection())).pack(fill='x',pady=2)
ttk.Separator(vault_btn_frame,orient='horizontal').pack(fill='x',pady=4)
ttk.Button(vault_btn_frame,text='Export Public',command=lambda: export_selected_vault('public')).pack(fill='x',pady=2)
ttk.Button(vault_btn_frame,text='Export Private',command=lambda: export_selected_vault('private')).pack(fill='x',pady=2)
ttk.Separator(vault_btn_frame,orient='horizontal').pack(fill='x',pady=4)
ttk.Button(vault_btn_frame,text='Load to Encrypt',command=lambda: load_vault_selection_into(public_key_var,public_key_text,'public')).pack(fill='x',pady=2)
ttk.Button(vault_btn_frame,text='Load to Decrypt',command=lambda: load_vault_selection_into(private_key_var,private_key_text,'private',passphrase_entry)).pack(fill='x',pady=2)
ttk.Button(vault_btn_frame,text='Load to Verify',command=lambda: load_vault_selection_into(verify_key_var,public_verify_key_text,'public')).pack(fill='x',pady=2)
ttk.Button(vault_btn_frame,text='Load to Sign',command=lambda: load_vault_selection_into(sign_key_var,private_sign_key_text,'private',passphrase_sign_entry)).pack(fill='x',pady=2)

vault_detail_frame=ttk.Frame(vault_container)
vault_detail_frame.pack(side='left',fill='both',expand=True,padx=5,pady=5)

vault_passphrase_var=tk.StringVar()

vault_private_section=ttk.LabelFrame(vault_detail_frame,text='Private Key')
vault_private_section.pack(fill='both',expand=True,padx=5,pady=5)
vault_private_text=scrolledtext.ScrolledText(vault_private_section,height=10)
vault_private_text.pack(fill='both',expand=True,padx=5,pady=5)
ttk.Button(vault_private_section,text='Save Private From Text',command=save_vault_private_from_text).pack(pady=2)

vault_public_section=ttk.LabelFrame(vault_detail_frame,text='Public Key')
vault_public_section.pack(fill='both',expand=True,padx=5,pady=5)
vault_public_text=scrolledtext.ScrolledText(vault_public_section,height=10)
vault_public_text.pack(fill='both',expand=True,padx=5,pady=5)
ttk.Button(vault_public_section,text='Save Public From Text',command=save_vault_public_from_text).pack(pady=2)

pass_frame=ttk.LabelFrame(vault_detail_frame,text='Stored Passphrase')
pass_frame.pack(fill='x',padx=5,pady=5)
ttk.Entry(pass_frame,textvariable=vault_passphrase_var,show='*').pack(fill='x',padx=5,pady=5)
pass_btns=ttk.Frame(pass_frame)
pass_btns.pack(fill='x',padx=5,pady=5)
ttk.Button(pass_btns,text='Save Passphrase',command=save_selected_passphrase).pack(side='left',padx=5)
ttk.Button(pass_btns,text='Clear Passphrase',command=clear_selected_passphrase).pack(side='left',padx=5)

pas_frame=ttk.LabelFrame(aes_frame,text='AES Mode')
pas_frame.pack(fill='x',padx=5,pady=5)
aes_mode_label=ttk.Label(pas_frame,text='AES mode: Master Password')
aes_mode_label.pack(side='left',padx=5,pady=5)
for l,c in (('Set AES Key',set_unique_aes),('Use Master Password',use_master_aes)):
    ttk.Button(pas_frame,text=l,command=c).pack(side='left',padx=5,pady=5)

plain_frame=ttk.LabelFrame(aes_frame,text='Plaintext')
plain_frame.pack(fill='both',expand=True,padx=5,pady=5)
aes_plaintext_text=scrolledtext.ScrolledText(plain_frame,height=10)
aes_plaintext_text.pack(fill='both',expand=True,padx=5,pady=5)

for l,c in (
    ('Encrypt',aes_encrypt_action),
    ('Decrypt',aes_decrypt_action),
    ('Encrypt File',aes_encrypt_file_action),
    ('Decrypt File',aes_decrypt_file_action),
    ('Copy Ciphertext',lambda: copy_to_clipboard(aes_ciphertext_text)),
    ('Save Ciphertext',save_aes_ciphertext_action)
):
    ttk.Button(aes_frame,text=l,command=c).pack(side='left',padx=5,pady=5)

cipher_frame=ttk.LabelFrame(aes_frame,text='Ciphertext')
cipher_frame.pack(fill='both',expand=True,padx=5,pady=5)
aes_ciphertext_text=scrolledtext.ScrolledText(cipher_frame,height=10)
aes_ciphertext_text.pack(fill='both',expand=True,padx=5,pady=5)

dec_frame=ttk.LabelFrame(aes_frame,text='Decrypted')
dec_frame.pack(fill='both',expand=True,padx=5,pady=5)
aes_decrypted_text=scrolledtext.ScrolledText(dec_frame,height=10)
aes_decrypted_text.pack(fill='both',expand=True,padx=5,pady=5)

cred_frame=ttk.Frame(nb)
nb.add(cred_frame,text='Credentials')
columns=('site','username','password')
tree=ttk.Treeview(cred_frame,columns=columns,show='headings')
for c in columns:
    tree.heading(c,text=c.capitalize())
tree.pack(fill='both',expand=True,padx=5,pady=5)
bf=ttk.Frame(cred_frame)
bf.pack(fill='x',padx=5,pady=5)
for l,c in (
    ('Add',add_cred),
    ('Edit',edit_cred),
    ('Delete',delete_cred),
    ('Show',show_password),
    ('Copy Password',lambda: copy_selected('password')),
    ('Copy Username',lambda: copy_selected('username'))
):
    ttk.Button(bf,text=l,command=c).pack(side='left')

cards_frame=ttk.Frame(nb)
nb.add(cards_frame,text='Cards')
card_columns=('holder','last4','expires')
card_tree=ttk.Treeview(cards_frame,columns=card_columns,show='headings')
for c in card_columns:
    card_tree.heading(c,text=c.capitalize())
card_tree.pack(fill='both',expand=True,padx=5,pady=5)
cbf=ttk.Frame(cards_frame)
cbf.pack(fill='x',padx=5,pady=5)
for l,c in (
    ('Add',add_card),
    ('Edit',edit_card),
    ('Delete',delete_card),
    ('Show',show_card),
    ('Copy Number',copy_card_number),
    ('Copy Exp Month',copy_exp_month),
    ('Copy Exp Year',copy_exp_year),
    ('Copy CVV',copy_cvv)
):
    ttk.Button(cbf,text=l,command=c).pack(side='left')

hash_frame=ttk.Frame(nb)
nb.add(hash_frame,text='Check Hash')
source_var=tk.StringVar(value='text')
for txt,val in (('Text','text'),('File','file')):
    ttk.Radiobutton(hash_frame,text=txt,variable=source_var,value=val).pack(anchor='w',padx=5,pady=5)

text_frame_hash=ttk.LabelFrame(hash_frame,text='Text Input')
text_frame_hash.pack(fill='both',expand=True,padx=5,pady=5)
hash_text_input=scrolledtext.ScrolledText(text_frame_hash,height=5)
hash_text_input.pack(fill='both',expand=True,padx=5,pady=5)

file_frame_hash=ttk.Frame(hash_frame)
file_frame_hash.pack(fill='x',padx=5,pady=5)
file_path_var=tk.StringVar()
ttk.Entry(file_frame_hash,textvariable=file_path_var).pack(side='left',fill='x',expand=True,padx=5,pady=5)
ttk.Button(file_frame_hash,text='Browse',command=lambda:(fp:=filedialog.askopenfilename()) and file_path_var.set(fp) or source_var.set('file')).pack(side='left',padx=5,pady=5)

md5_var=tk.StringVar()
sha256_var=tk.StringVar()

def compute_hashes():
    if source_var.get()=='text':
        data=hash_text_input.get('1.0',tk.END).encode()
        md5_var.set(hashlib.md5(data).hexdigest())
        sha256_var.set(hashlib.sha256(data).hexdigest())
        return
    fp=file_path_var.get()
    if not fp:
        messagebox.showerror('Error','No file selected')
        return
    md5=hashlib.md5()
    sha=hashlib.sha256()
    with open(fp,'rb') as f:
        for chunk in iter(lambda:f.read(1<<18),b''):
            md5.update(chunk)
            sha.update(chunk)
    md5_var.set(md5.hexdigest())
    sha256_var.set(sha.hexdigest())

verify_alg_var=tk.StringVar(value='MD5')

def verify_hash():
    h=verify_entry_hash.get().strip().lower()
    if not h:
        messagebox.showerror('Error','No hash to verify')
        return
    comp=(md5_var if verify_alg_var.get()=='MD5' else sha256_var).get().lower()
    messagebox.showinfo('Result','CONGRATS these hash match!' if h==comp else 'WARNING no hash verification')

ttk.Button(hash_frame,text='Compute Hashes',command=compute_hashes).pack(padx=5,pady=5)
for l,v in (('MD5:',md5_var),('SHA256:',sha256_var)):
    ttk.Label(hash_frame,text=l).pack(anchor='w',padx=5,pady=2)
    ttk.Entry(hash_frame,textvariable=v,state='readonly').pack(fill='x',padx=5,pady=2)
vf=ttk.Frame(hash_frame)
vf.pack(fill='x',padx=5,pady=5)
verify_entry_hash=ttk.Entry(vf)
verify_entry_hash.pack(side='left',fill='x',expand=True,padx=5,pady=5)
ttk.OptionMenu(vf,verify_alg_var,'MD5','MD5','SHA256').pack(side='left',padx=5,pady=5)
ttk.Button(hash_frame,text='Verify Hash',command=verify_hash).pack(padx=5,pady=5)

def refresh_credentials_view():
    for i in tree.get_children():
        tree.delete(i)
    for s,i in load_credentials().items():
        tree.insert('', 'end', iid=s, values=(s,i['username'],'******'))

def refresh_cards_view():
    for i in card_tree.get_children():
        card_tree.delete(i)
    for n,i in load_cards().items():
        card_tree.insert('', 'end', iid=n, values=(i['holder'],n[-4:],i['expires']))

refresh_key_dropdowns()
refresh_vault_view()
refresh_credentials_view()
refresh_cards_view()

def on_close():
    backend.master_password = None
    backend.fernet = None
    backend.aes_secret_key = None
    root.destroy()

root.protocol('WM_DELETE_WINDOW',on_close)

try:
    root.mainloop()
finally:
    backend.master_password = None
    backend.fernet = None
    backend.aes_secret_key = None
