#!/usr/bin/env python3
"""
Busca palavras-chave em arquivos GIGANTES (171GB+, bilhões de linhas)
Otimizado para processar sem travar memória
"""

import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
from datetime import datetime
import threading


class MassiveSearchApp:
    def __init__(self, root):
        self.root = root
        self.root.title("🔍 Busca em Arquivos Gigantes")
        self.root.geometry("1000x750")
        self.root.configure(bg='#1a1a1a')

        # Variáveis
        self.file_path = tk.StringVar(value="/Volumes/Novo volume/lista.txt")
        self.keyword = tk.StringVar()
        self.output_path = tk.StringVar()
        self.max_results = tk.IntVar(value=10000)  # Limite de resultados
        self.chunk_size = tk.IntVar(value=1000000)  # 1MB chunks
        self.show_all_matches = tk.BooleanVar(value=False)  # Mostrar todas ou apenas primeiras 10
        self.is_searching = False
        self.stop_requested = False

        self.setup_ui()

    def setup_ui(self):
        """Configura interface"""

        # Header
        header_frame = tk.Frame(self.root, bg='#ef4444', height=80)
        header_frame.pack(fill='x', padx=0, pady=0)
        header_frame.pack_propagate(False)

        title_label = tk.Label(
            header_frame,
            text="🔍 BUSCA EM ARQUIVOS GIGANTES (171GB+)",
            font=('Helvetica', 20, 'bold'),
            bg='#ef4444',
            fg='white'
        )
        title_label.pack(pady=25)

        # Main container
        main_frame = tk.Frame(self.root, bg='#1a1a1a')
        main_frame.pack(fill='both', expand=True, padx=20, pady=20)

        # Arquivo de entrada
        input_frame = tk.LabelFrame(
            main_frame,
            text="📁 Arquivo de Entrada (171GB - 3 bilhões de linhas)",
            font=('Helvetica', 12, 'bold'),
            bg='#2a2a2a',
            fg='#ffffff',
            padx=15,
            pady=15
        )
        input_frame.pack(fill='x', pady=(0, 15))

        file_entry = tk.Entry(
            input_frame,
            textvariable=self.file_path,
            font=('Helvetica', 11),
            bg='#3a3a3a',
            fg='#ffffff',
            insertbackground='white',
            relief='flat',
            bd=0
        )
        file_entry.pack(side='left', fill='x', expand=True, padx=(0, 10), ipady=8)

        browse_btn = tk.Button(
            input_frame,
            text="📂 Procurar",
            command=self.browse_file,
            font=('Helvetica', 10, 'bold'),
            bg='#0ea5e9',
            fg='white',
            relief='flat',
            padx=20,
            pady=8,
            cursor='hand2'
        )
        browse_btn.pack(side='right')

        # Info do arquivo
        self.file_info_label = tk.Label(
            input_frame,
            text="",
            font=('Helvetica', 9),
            bg='#2a2a2a',
            fg='#888888',
            anchor='w'
        )
        self.file_info_label.pack(fill='x', pady=(5, 0))

        # Palavra-chave
        keyword_frame = tk.LabelFrame(
            main_frame,
            text="🔎 Palavra-Chave",
            font=('Helvetica', 12, 'bold'),
            bg='#2a2a2a',
            fg='#ffffff',
            padx=15,
            pady=15
        )
        keyword_frame.pack(fill='x', pady=(0, 15))

        keyword_entry = tk.Entry(
            keyword_frame,
            textvariable=self.keyword,
            font=('Helvetica', 14),
            bg='#3a3a3a',
            fg='#10b981',
            insertbackground='#10b981',
            relief='flat',
            bd=0
        )
        keyword_entry.pack(fill='x', ipady=10)

        # Focus no campo de palavra-chave ao iniciar
        keyword_entry.focus_set()

        hint_label = tk.Label(
            keyword_frame,
            text="💡 Digite a palavra-chave e pressione Enter ou clique em 'Iniciar Busca'",
            font=('Helvetica', 9, 'italic'),
            bg='#2a2a2a',
            fg='#888888'
        )
        hint_label.pack(anchor='w', pady=(5, 0))

        # Bind Enter key para iniciar busca
        keyword_entry.bind('<Return>', lambda e: self.start_search())

        # Configurações avançadas
        config_frame = tk.LabelFrame(
            main_frame,
            text="⚙️ Configurações Avançadas",
            font=('Helvetica', 12, 'bold'),
            bg='#2a2a2a',
            fg='#ffffff',
            padx=15,
            pady=15
        )
        config_frame.pack(fill='x', pady=(0, 15))

        # Limite de resultados
        limit_frame = tk.Frame(config_frame, bg='#2a2a2a')
        limit_frame.pack(fill='x', pady=(0, 10))

        tk.Label(
            limit_frame,
            text="📊 Limite máximo de resultados:",
            font=('Helvetica', 10),
            bg='#2a2a2a',
            fg='#ffffff'
        ).pack(side='left', padx=(0, 10))

        limit_spinbox = tk.Spinbox(
            limit_frame,
            from_=1000,
            to=1000000,
            increment=10000,
            textvariable=self.max_results,
            font=('Helvetica', 10),
            bg='#3a3a3a',
            fg='#ffffff',
            relief='flat',
            width=10
        )
        limit_spinbox.pack(side='left')

        tk.Label(
            limit_frame,
            text="(previne travar ao salvar milhões de linhas)",
            font=('Helvetica', 9, 'italic'),
            bg='#2a2a2a',
            fg='#888888'
        ).pack(side='left', padx=(10, 0))

        # Checkbox para mostrar todas as linhas
        show_all_frame = tk.Frame(config_frame, bg='#2a2a2a')
        show_all_frame.pack(fill='x', pady=(0, 10))

        show_all_check = tk.Checkbutton(
            show_all_frame,
            text="📺 Mostrar TODAS as linhas encontradas em tempo real (pode ficar lento se encontrar muitas)",
            variable=self.show_all_matches,
            font=('Helvetica', 10),
            bg='#2a2a2a',
            fg='#ffffff',
            selectcolor='#3a3a3a',
            activebackground='#2a2a2a',
            activeforeground='#ffffff'
        )
        show_all_check.pack(anchor='w')

        # Arquivo de saída
        output_frame = tk.Frame(config_frame, bg='#2a2a2a')
        output_frame.pack(fill='x')

        tk.Label(
            output_frame,
            text="💾 Arquivo de saída:",
            font=('Helvetica', 10),
            bg='#2a2a2a',
            fg='#ffffff'
        ).pack(side='left', padx=(0, 10))

        output_entry = tk.Entry(
            output_frame,
            textvariable=self.output_path,
            font=('Helvetica', 10),
            bg='#3a3a3a',
            fg='#ffffff',
            insertbackground='white',
            relief='flat',
            bd=0,
            width=40
        )
        output_entry.pack(side='left', padx=(0, 10), ipady=5)

        output_browse_btn = tk.Button(
            output_frame,
            text="💾 Escolher",
            command=self.browse_output,
            font=('Helvetica', 9, 'bold'),
            bg='#10b981',
            fg='white',
            relief='flat',
            padx=15,
            pady=5,
            cursor='hand2'
        )
        output_browse_btn.pack(side='left')

        # Botões de ação
        button_frame = tk.Frame(main_frame, bg='#1a1a1a')
        button_frame.pack(pady=(0, 15))

        self.search_btn = tk.Button(
            button_frame,
            text="🚀 INICIAR BUSCA",
            command=self.start_search,
            font=('Helvetica', 14, 'bold'),
            bg='#0ea5e9',
            fg='white',
            relief='flat',
            padx=30,
            pady=15,
            cursor='hand2',
            width=20
        )
        self.search_btn.pack(side='left', padx=(0, 10))

        self.stop_btn = tk.Button(
            button_frame,
            text="⏹ PARAR",
            command=self.stop_search,
            font=('Helvetica', 14, 'bold'),
            bg='#ef4444',
            fg='white',
            relief='flat',
            padx=30,
            pady=15,
            cursor='hand2',
            state='disabled',
            width=15
        )
        self.stop_btn.pack(side='left')

        # Barra de progresso
        progress_frame = tk.Frame(main_frame, bg='#2a2a2a')
        progress_frame.pack(fill='x', pady=(0, 15))

        self.progress_bar = ttk.Progressbar(
            progress_frame,
            mode='determinate',
            length=400
        )
        self.progress_bar.pack(fill='x', padx=10, pady=10)

        self.progress_label = tk.Label(
            progress_frame,
            text="Pronto para iniciar",
            font=('Helvetica', 10),
            bg='#2a2a2a',
            fg='#ffffff'
        )
        self.progress_label.pack(pady=(0, 10))

        # Resultados
        results_frame = tk.LabelFrame(
            main_frame,
            text="📊 Resultados em Tempo Real",
            font=('Helvetica', 12, 'bold'),
            bg='#2a2a2a',
            fg='#ffffff',
            padx=15,
            pady=15
        )
        results_frame.pack(fill='both', expand=True)

        self.results_text = scrolledtext.ScrolledText(
            results_frame,
            font=('Courier', 9),
            bg='#0a0a0a',
            fg='#00ff00',
            insertbackground='#00ff00',
            relief='flat',
            wrap='word',
            state='disabled'
        )
        self.results_text.pack(fill='both', expand=True)

        # Tags para cores
        self.results_text.tag_config('header', foreground='#0ea5e9', font=('Courier', 10, 'bold'))
        self.results_text.tag_config('success', foreground='#10b981', font=('Courier', 9, 'bold'))
        self.results_text.tag_config('warning', foreground='#f59e0b')
        self.results_text.tag_config('error', foreground='#ef4444', font=('Courier', 9, 'bold'))
        self.results_text.tag_config('info', foreground='#00ff00')
        self.results_text.tag_config('dim', foreground='#666666')

        # Atualiza info do arquivo ao iniciar
        self.update_file_info()

    def update_file_info(self):
        """Atualiza informações do arquivo"""
        file_path = self.file_path.get()
        if os.path.exists(file_path):
            size = os.path.getsize(file_path)
            size_gb = size / (1024**3)
            self.file_info_label.config(
                text=f"📊 Tamanho: {size_gb:.2f} GB ({size:,} bytes)"
            )
        else:
            self.file_info_label.config(text="⚠️ Arquivo não encontrado")

    def browse_file(self):
        """Abre diálogo para selecionar arquivo"""
        filename = filedialog.askopenfilename(
            title="Selecione o arquivo",
            initialdir="/Volumes",
            filetypes=[("Arquivos de texto", "*.txt"), ("Todos os arquivos", "*.*")]
        )
        if filename:
            self.file_path.set(filename)
            self.update_file_info()

    def browse_output(self):
        """Abre diálogo para selecionar arquivo de saída"""
        filename = filedialog.asksaveasfilename(
            title="Salvar resultados como",
            defaultextension=".txt",
            filetypes=[("Arquivos de texto", "*.txt")],
            initialfile=f"resultados_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
        )
        if filename:
            self.output_path.set(filename)

    def log(self, message, tag='info'):
        """Adiciona mensagem ao console"""
        self.results_text.configure(state='normal')
        self.results_text.insert('end', message + '\n', tag)
        self.results_text.see('end')
        self.results_text.configure(state='disabled')
        self.root.update()

    def clear_results(self):
        """Limpa console"""
        self.results_text.configure(state='normal')
        self.results_text.delete('1.0', 'end')
        self.results_text.configure(state='disabled')

    def update_progress(self, current_bytes, total_bytes, lines_processed, matches_found):
        """Atualiza barra de progresso"""
        progress = (current_bytes / total_bytes) * 100
        self.progress_bar['value'] = progress

        current_gb = current_bytes / (1024**3)
        total_gb = total_bytes / (1024**3)

        self.progress_label.config(
            text=f"📊 {current_gb:.2f} GB / {total_gb:.2f} GB ({progress:.1f}%) | "
                 f"📄 {lines_processed:,} linhas | ✅ {matches_found:,} encontradas"
        )
        self.root.update()

    def stop_search(self):
        """Solicita parada da busca"""
        self.stop_requested = True
        self.stop_btn.configure(state='disabled', text='⏹ PARANDO...')
        self.log("⚠️ Solicitação de parada recebida. Finalizando...", 'warning')

    def start_search(self):
        """Inicia busca"""
        if self.is_searching:
            messagebox.showwarning("Aviso", "Uma busca já está em andamento!")
            return

        # Validações
        if not self.file_path.get():
            messagebox.showerror("Erro", "Selecione um arquivo!")
            return

        if not os.path.exists(self.file_path.get()):
            messagebox.showerror("Erro", f"Arquivo não encontrado:\n{self.file_path.get()}")
            return

        if not self.keyword.get().strip():
            messagebox.showerror("Erro", "Digite uma palavra-chave!")
            return

        # Define arquivo de saída
        if not self.output_path.get():
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            self.output_path.set(f"/Users/neog/resultados_{timestamp}.txt")

        # Limpa resultados
        self.clear_results()
        self.stop_requested = False

        # Desabilita/habilita botões
        self.search_btn.configure(state='disabled')
        self.stop_btn.configure(state='normal', text='⏹ PARAR')

        self.is_searching = True

        # Executa em thread
        thread = threading.Thread(target=self.perform_search, daemon=True)
        thread.start()

    def perform_search(self):
        """Executa busca otimizada para arquivos gigantes"""
        try:
            file_path = self.file_path.get()
            keyword = self.keyword.get().strip().lower()
            output_file = self.output_path.get()
            max_results = self.max_results.get()

            # Info do arquivo
            file_size = os.path.getsize(file_path)
            file_size_gb = file_size / (1024**3)

            self.log("=" * 90, 'header')
            self.log("🚀 BUSCA EM ARQUIVO GIGANTE INICIADA", 'header')
            self.log("=" * 90, 'header')
            self.log(f"📁 Arquivo: {file_path}", 'info')
            self.log(f"📊 Tamanho: {file_size_gb:.2f} GB ({file_size:,} bytes)", 'info')
            self.log(f"🔎 Palavra-chave: '{keyword}'", 'info')
            self.log(f"💾 Saída: {output_file}", 'info')
            self.log(f"🎯 Limite de resultados: {max_results:,}", 'info')
            self.log("=" * 90, 'header')
            self.log("")

            # Estatísticas
            lines_processed = 0
            matches_found = 0
            bytes_read = 0
            start_time = datetime.now()

            # Abre arquivos (streaming)
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as infile, \
                 open(output_file, 'w', encoding='utf-8') as outfile:

                # Escreve cabeçalho
                outfile.write(f"Resultados da busca por: '{keyword}'\n")
                outfile.write(f"Arquivo: {file_path}\n")
                outfile.write(f"Tamanho: {file_size_gb:.2f} GB\n")
                outfile.write(f"Data: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
                outfile.write(f"Limite de resultados: {max_results:,}\n")
                outfile.write("=" * 90 + "\n\n")

                # Processa linha por linha (streaming - não carrega tudo na memória)
                for line_num, line in enumerate(infile, 1):
                    # Verifica se deve parar
                    if self.stop_requested:
                        self.log("", 'warning')
                        self.log("⚠️ BUSCA INTERROMPIDA PELO USUÁRIO", 'warning')
                        break

                    lines_processed += 1
                    bytes_read += len(line.encode('utf-8'))

                    # Busca case-insensitive
                    if keyword in line.lower():
                        matches_found += 1

                        # Escreve no arquivo de saída
                        outfile.write(f"Linha {line_num:,}: {line}")
                        outfile.flush()  # Força escrita imediata

                        # Mostra no console baseado na opção do usuário
                        show_in_console = self.show_all_matches.get() or matches_found <= 20

                        if show_in_console:
                            # Preview da linha com mais contexto
                            preview = line.rstrip('\n')[:120]
                            if len(line.rstrip('\n')) > 120:
                                preview += "..."

                            # Destaca a palavra-chave encontrada (primeira ocorrência)
                            keyword_pos = line.lower().find(keyword)
                            if keyword_pos >= 0:
                                found_keyword = line[keyword_pos:keyword_pos+len(keyword)]
                                self.log(
                                    f"✓ [{matches_found:,}] Linha {line_num:,} → ...{preview}",
                                    'success'
                                )
                            else:
                                self.log(f"✓ [{matches_found:,}] Linha {line_num:,}: {preview}", 'success')

                        # Para se atingir limite
                        if matches_found >= max_results:
                            self.log("", 'warning')
                            self.log(f"⚠️ LIMITE DE {max_results:,} RESULTADOS ATINGIDO", 'warning')
                            self.log("⚠️ Parando busca para prevenir travamento...", 'warning')
                            break

                    # Atualiza progresso a cada 50.000 linhas (mais frequente)
                    if line_num % 50000 == 0:
                        self.update_progress(bytes_read, file_size, lines_processed, matches_found)

                    # Log detalhado a cada 500.000 linhas
                    if line_num % 500000 == 0:
                        elapsed = (datetime.now() - start_time).total_seconds()
                        rate = lines_processed / elapsed if elapsed > 0 else 0
                        gb_processed = bytes_read / (1024**3)

                        self.log("", 'dim')
                        self.log(
                            f"⏳ CHECKPOINT: {lines_processed:,} linhas processadas | "
                            f"✅ {matches_found:,} encontradas | "
                            f"📊 {gb_processed:.2f} GB | "
                            f"⚡ {rate:,.0f} linhas/seg",
                            'warning'
                        )

                        # Estimativa de tempo restante
                        if rate > 0 and bytes_read > 0:
                            remaining_bytes = file_size - bytes_read
                            remaining_time = (remaining_bytes / bytes_read) * elapsed
                            hours = int(remaining_time // 3600)
                            minutes = int((remaining_time % 3600) // 60)
                            self.log(
                                f"⏰ Tempo restante estimado: {hours}h {minutes}min",
                                'dim'
                            )
                        self.log("", 'dim')

            # Atualiza progresso final
            self.update_progress(bytes_read, file_size, lines_processed, matches_found)

            # Estatísticas finais
            elapsed = (datetime.now() - start_time).total_seconds()
            rate = lines_processed / elapsed if elapsed > 0 else 0

            self.log("", 'info')
            self.log("=" * 90, 'header')
            self.log("📊 ESTATÍSTICAS FINAIS", 'header')
            self.log("=" * 90, 'header')
            self.log(f"📄 Linhas processadas: {lines_processed:,}", 'info')
            self.log(f"✅ Linhas encontradas: {matches_found:,}", 'success')
            self.log(f"📊 GB processados: {bytes_read / (1024**3):.2f} GB", 'info')
            self.log(f"⏱️ Tempo decorrido: {elapsed:.1f} segundos", 'info')
            self.log(f"⚡ Velocidade: {rate:,.0f} linhas/segundo", 'info')
            if matches_found > 0:
                self.log(f"📈 Taxa de correspondência: {(matches_found/lines_processed*100):.4f}%", 'info')
            self.log("", 'info')
            self.log(f"💾 Resultados salvos em:", 'success')
            self.log(f"   {output_file}", 'success')
            self.log("=" * 90, 'header')
            self.log("✅ BUSCA CONCLUÍDA!", 'success')
            self.log("=" * 90, 'header')

            # Mensagem final
            if not self.stop_requested:
                messagebox.showinfo(
                    "Sucesso!",
                    f"Busca concluída!\n\n"
                    f"Linhas processadas: {lines_processed:,}\n"
                    f"Linhas encontradas: {matches_found:,}\n"
                    f"Tempo: {elapsed:.1f}s\n"
                    f"Velocidade: {rate:,.0f} linhas/s\n\n"
                    f"Resultados salvos em:\n{output_file}"
                )

        except Exception as e:
            self.log("", 'error')
            self.log(f"❌ ERRO: {str(e)}", 'error')
            messagebox.showerror("Erro", f"Erro ao processar arquivo:\n{str(e)}")

        finally:
            # Reabilita botões
            self.search_btn.configure(state='normal')
            self.stop_btn.configure(state='disabled', text='⏹ PARAR')
            self.is_searching = False
            self.stop_requested = False


def main():
    root = tk.Tk()
    app = MassiveSearchApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()
