#!/usr/bin/env python3
"""
Instagram Story Viewers Extractor
Extrai perfis de pessoas que visualizaram seus stories via Safari DevTools.

Uso:
1. Abra o Safari e vá para instagram.com
2. Faça login na sua conta
3. Abra seu story e clique para ver a lista de viewers
4. Execute este script

Requisitos:
- Safari com "Allow JavaScript from Apple Events" habilitado
  (Safari > Develop > Allow JavaScript from Apple Events)
"""

import subprocess
import json
import csv
import os
from datetime import datetime
from pathlib import Path
import time
import re

# Diretório para salvar os dados
OUTPUT_DIR = Path.home() / "Documents" / "instagram-data"
OUTPUT_DIR.mkdir(exist_ok=True)

def run_applescript(script: str) -> str:
    """Executa AppleScript e retorna o resultado."""
    result = subprocess.run(
        ["osascript", "-e", script],
        capture_output=True,
        text=True
    )
    if result.returncode != 0:
        raise Exception(f"AppleScript error: {result.stderr}")
    return result.stdout.strip()

def check_safari_devtools():
    """Verifica se o Safari está configurado corretamente."""
    check_script = '''
    tell application "Safari"
        if (count of windows) = 0 then
            return "NO_WINDOW"
        end if
        return "OK"
    end tell
    '''
    try:
        result = run_applescript(check_script)
        if result == "NO_WINDOW":
            print("❌ Nenhuma janela do Safari aberta.")
            print("   Abra o Safari e navegue até seu story no Instagram.")
            return False
        return True
    except Exception as e:
        print(f"❌ Erro ao verificar Safari: {e}")
        print("\n⚠️  Verifique se o Safari Developer está habilitado:")
        print("   Safari > Settings > Advanced > Show features for web developers")
        print("   Safari > Develop > Allow JavaScript from Apple Events")
        return False

def get_current_url() -> str:
    """Obtém a URL atual do Safari."""
    script = '''
    tell application "Safari"
        return URL of current tab of window 1
    end tell
    '''
    return run_applescript(script)

def extract_story_viewers() -> list:
    """Extrai os viewers do story via JavaScript no Safari."""

    # JavaScript otimizado para o formato do Instagram
    js_code = '''
    (function() {
        let viewers = [];

        // Busca todas as imagens de perfil com alt text que termina em "'s profile picture"
        let profilePics = document.querySelectorAll('img[alt$="\\'s profile picture"]');

        profilePics.forEach(img => {
            let alt = img.getAttribute('alt') || '';
            let match = alt.match(/^(.+)'s profile picture$/);

            if (match) {
                let username = match[1];

                // Busca o elemento pai que contém o nome completo
                let container = img.closest('div[role="button"]') ||
                              img.closest('a[href]') ||
                              img.closest('div');

                let fullName = '';
                let profileUrl = 'https://instagram.com/' + username;

                if (container) {
                    // Busca todos os spans no container
                    let spans = container.querySelectorAll('span');
                    spans.forEach(span => {
                        let text = span.textContent.trim();
                        // O nome completo geralmente não é igual ao username
                        if (text && text !== username && text.length > 0 && text.length < 100) {
                            if (!fullName || text.length > fullName.length) {
                                fullName = text;
                            }
                        }
                    });

                    // Busca link do perfil
                    let link = container.closest('a[href^="/"]') ||
                              container.querySelector('a[href^="/"]');
                    if (link) {
                        let href = link.getAttribute('href');
                        if (href && href.startsWith('/')) {
                            profileUrl = 'https://instagram.com' + href;
                        }
                    }
                }

                viewers.push({
                    username: username,
                    name: fullName,
                    profile_url: profileUrl,
                    profile_pic: img.src
                });
            }
        });

        // Método alternativo: buscar por estrutura de lista de viewers
        if (viewers.length === 0) {
            let dialog = document.querySelector('div[role="dialog"]');
            if (dialog) {
                let links = dialog.querySelectorAll('a[href^="/"]');
                links.forEach(a => {
                    let href = a.getAttribute('href');
                    if (href && href.match(/^\\/[^/]+\\/?$/)) {
                        let username = href.replace(/\\//g, '');
                        if (username && !['explore', 'direct', 'accounts', 'p', 'reel'].includes(username)) {
                            let img = a.querySelector('img');
                            let spans = a.querySelectorAll('span');
                            let name = '';
                            spans.forEach(s => {
                                if (s.textContent !== username && s.textContent.length > 0) {
                                    name = s.textContent;
                                }
                            });

                            viewers.push({
                                username: username,
                                name: name,
                                profile_url: 'https://instagram.com/' + username,
                                profile_pic: img ? img.src : ''
                            });
                        }
                    }
                });
            }
        }

        // Remove duplicatas mantendo a ordem
        let unique = [];
        let seen = new Set();
        viewers.forEach(v => {
            if (!seen.has(v.username)) {
                seen.add(v.username);
                unique.push(v);
            }
        });

        return JSON.stringify(unique);
    })();
    '''

    js_escaped = js_code.replace('\\', '\\\\').replace('"', '\\"').replace('\n', ' ')

    script = f'''
    tell application "Safari"
        set jsResult to do JavaScript "{js_escaped}" in current tab of window 1
        return jsResult
    end tell
    '''

    result = run_applescript(script)

    try:
        viewers = json.loads(result)
        return viewers
    except json.JSONDecodeError:
        print(f"⚠️  Erro ao parsear: {result[:200]}")
        return []

def get_page_text() -> str:
    """Obtém o texto visível da página."""
    js_code = '''
    (function() {
        let dialog = document.querySelector('div[role="dialog"]');
        if (dialog) {
            return dialog.innerText;
        }
        return document.body.innerText;
    })();
    '''

    js_escaped = js_code.replace('"', '\\"').replace('\n', ' ')

    script = f'''
    tell application "Safari"
        set jsResult to do JavaScript "{js_escaped}" in current tab of window 1
        return jsResult
    end tell
    '''

    return run_applescript(script)

def parse_viewers_from_text(text: str) -> list:
    """Parseia viewers a partir do texto da página (fallback)."""
    viewers = []
    lines = text.split('\n')

    i = 0
    while i < len(lines):
        line = lines[i].strip()

        # Procura padrão: "username's profile picture"
        if line.endswith("'s profile picture"):
            username = line.replace("'s profile picture", "").strip()

            # Próxima linha deve ser o username novamente
            # E a seguinte o nome completo
            name = ""
            if i + 2 < len(lines):
                next_line = lines[i + 1].strip()
                name_line = lines[i + 2].strip()

                if next_line == username:
                    name = name_line
                    i += 2

            viewers.append({
                "username": username,
                "name": name,
                "profile_url": f"https://instagram.com/{username}",
                "profile_pic": ""
            })

        i += 1

    return viewers

def scroll_viewers_list(direction: str = "down"):
    """Rola a lista de viewers."""
    scroll_amount = 500 if direction == "down" else -500

    js_code = f'''
    (function() {{
        let dialog = document.querySelector('div[role="dialog"]');
        if (dialog) {{
            let scrollable = dialog.querySelector('div[style*="overflow"]') ||
                           dialog.querySelector('ul')?.parentElement ||
                           dialog;

            // Tenta encontrar o elemento scrollável
            let elements = dialog.querySelectorAll('div');
            for (let el of elements) {{
                if (el.scrollHeight > el.clientHeight) {{
                    el.scrollTop += {scroll_amount};
                    return "scrolled";
                }}
            }}
        }}

        // Fallback: scroll na janela
        window.scrollBy(0, {scroll_amount});
        return "window_scroll";
    }})();
    '''

    js_escaped = js_code.replace('"', '\\"').replace('\n', ' ')

    script = f'''
    tell application "Safari"
        do JavaScript "{js_escaped}" in current tab of window 1
    end tell
    '''

    run_applescript(script)

def save_viewers(viewers: list, story_id: str = "story"):
    """Salva os viewers em JSON e CSV."""
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

    # JSON
    json_file = OUTPUT_DIR / f"viewers_{story_id}_{timestamp}.json"
    with open(json_file, 'w', encoding='utf-8') as f:
        json.dump({
            "extracted_at": datetime.now().isoformat(),
            "story_id": story_id,
            "count": len(viewers),
            "viewers": viewers
        }, f, indent=2, ensure_ascii=False)

    # CSV
    csv_file = OUTPUT_DIR / f"viewers_{story_id}_{timestamp}.csv"
    with open(csv_file, 'w', newline='', encoding='utf-8') as f:
        writer = csv.DictWriter(f, fieldnames=['username', 'name', 'profile_url', 'profile_pic'])
        writer.writeheader()
        writer.writerows(viewers)

    return json_file, csv_file

def extract_all_with_scroll(max_scrolls: int = 20) -> list:
    """Extrai todos os viewers rolando a lista."""
    all_viewers = {}

    print("   Extraindo viewers com scroll automático...")

    for i in range(max_scrolls):
        # Extrai viewers visíveis
        viewers = extract_story_viewers()

        # Se não encontrou via JS, tenta via texto
        if not viewers:
            text = get_page_text()
            viewers = parse_viewers_from_text(text)

        new_count = 0
        for v in viewers:
            if v['username'] not in all_viewers:
                all_viewers[v['username']] = v
                new_count += 1

        print(f"   Scroll {i+1}: +{new_count} novos (total: {len(all_viewers)})")

        if new_count == 0 and i > 2:
            # Nenhum novo viewer após alguns scrolls
            break

        # Rola para baixo
        scroll_viewers_list("down")
        time.sleep(0.8)  # Aguarda carregamento

    return list(all_viewers.values())

def monitor_mode(interval: int = 30):
    """Modo de monitoramento contínuo para novos viewers."""
    print(f"\n🔄 Modo monitor: verificando a cada {interval} segundos")
    print("   Pressione Ctrl+C para parar\n")

    all_viewers = {}
    session_start = datetime.now()

    try:
        while True:
            viewers = extract_story_viewers()
            if not viewers:
                text = get_page_text()
                viewers = parse_viewers_from_text(text)

            new_this_round = []
            for v in viewers:
                if v['username'] not in all_viewers:
                    all_viewers[v['username']] = v
                    v['first_seen'] = datetime.now().isoformat()
                    new_this_round.append(v)
                    print(f"   ✨ Novo: @{v['username']} - {v.get('name', '')}")

            if new_this_round:
                elapsed = (datetime.now() - session_start).seconds // 60
                print(f"   📊 Total: {len(all_viewers)} viewers ({elapsed} min)")

            time.sleep(interval)

    except KeyboardInterrupt:
        print(f"\n\n📊 Sessão encerrada")
        print(f"   Duração: {(datetime.now() - session_start).seconds // 60} minutos")
        print(f"   Total capturado: {len(all_viewers)} viewers")

        if all_viewers:
            json_file, csv_file = save_viewers(list(all_viewers.values()), "monitor")
            print(f"\n💾 Salvo em: {json_file}")

def print_viewers(viewers: list, limit: int = 30):
    """Imprime lista formatada de viewers."""
    print(f"\n{'='*60}")
    print(f"{'#':>3} | {'@username':<25} | Nome")
    print(f"{'='*60}")

    for i, v in enumerate(viewers[:limit], 1):
        name = v.get('name', '')[:25]
        username = v['username'][:25]
        print(f"{i:>3} | @{username:<24} | {name}")

    if len(viewers) > limit:
        print(f"{'='*60}")
        print(f"... e mais {len(viewers) - limit} viewers")

# =============================================================================
# MULTI-STORIES FUNCTIONS
# =============================================================================

def click_seen_by() -> bool:
    """Clica no 'Seen by X' para abrir a lista de viewers."""
    js_code = '''
    (function() {
        var elements = document.querySelectorAll('*');
        for (var i = 0; i < elements.length; i++) {
            var el = elements[i];
            if (el.innerText && el.innerText.match(/^Seen by \\d+$/)) {
                el.click();
                return 'CLICKED: ' + el.innerText;
            }
        }
        var buttons = document.querySelectorAll('div[role="button"]');
        for (var j = 0; j < buttons.length; j++) {
            var btn = buttons[j];
            if (btn.innerText && btn.innerText.indexOf('Seen by') > -1) {
                btn.click();
                return 'CLICKED_BUTTON: ' + btn.innerText;
            }
        }
        return 'NOT_FOUND';
    })();
    '''
    js_escaped = js_code.replace('\\', '\\\\').replace('"', '\\"').replace('\n', ' ')
    script = f'''
    tell application "Safari"
        set jsResult to do JavaScript "{js_escaped}" in current tab of window 1
        return jsResult
    end tell
    '''
    try:
        result = run_applescript(script)
        return result.startswith('CLICKED')
    except:
        return False

def close_viewers_modal():
    """Fecha o modal de viewers (pressiona ESC)."""
    js_code = '''
    (function() {
        var event = new KeyboardEvent('keydown', {
            key: 'Escape',
            code: 'Escape',
            keyCode: 27,
            which: 27,
            bubbles: true
        });
        document.dispatchEvent(event);
        return 'ESC_SENT';
    })();
    '''
    js_escaped = js_code.replace('"', '\\"').replace('\n', ' ')
    script = f'''
    tell application "Safari"
        do JavaScript "{js_escaped}" in current tab of window 1
    end tell
    '''
    run_applescript(script)

def navigate_to_next_story() -> bool:
    """Navega para o próximo story. Retorna False se não há mais stories."""
    js_code = '''
    (function() {
        // Tenta encontrar botão Next
        var nextBtn = document.querySelector('button[aria-label*="Next"]') ||
                      document.querySelector('[aria-label="Next story"]');

        if (nextBtn) {
            nextBtn.click();
            return 'CLICKED_NEXT';
        }

        // Fallback: simula tecla direita
        var event = new KeyboardEvent('keydown', {
            key: 'ArrowRight',
            code: 'ArrowRight',
            keyCode: 39,
            which: 39,
            bubbles: true
        });
        document.dispatchEvent(event);
        return 'ARROW_RIGHT';
    })();
    '''
    js_escaped = js_code.replace('"', '\\"').replace('\n', ' ')
    script = f'''
    tell application "Safari"
        set jsResult to do JavaScript "{js_escaped}" in current tab of window 1
        return jsResult
    end tell
    '''
    try:
        result = run_applescript(script)
        return result != 'NO_NEXT'
    except:
        return False

def has_more_stories() -> bool:
    """Verifica se há mais stories disponíveis."""
    js_code = '''
    (function() {
        var nextBtn = document.querySelector('button[aria-label*="Next"]') ||
                      document.querySelector('[aria-label="Next story"]');
        return nextBtn ? 'HAS_MORE' : 'NO_MORE';
    })();
    '''
    js_escaped = js_code.replace('"', '\\"').replace('\n', ' ')
    script = f'''
    tell application "Safari"
        set jsResult to do JavaScript "{js_escaped}" in current tab of window 1
        return jsResult
    end tell
    '''
    try:
        result = run_applescript(script)
        return result == 'HAS_MORE'
    except:
        return False

def extract_all_stories(max_stories: int = 20) -> dict:
    """Extrai viewers de TODOS os stories automaticamente."""
    all_data = {
        "extracted_at": datetime.now().isoformat(),
        "stories": []
    }

    story_num = 0
    total_viewers = 0

    print(f"\n🔄 Iniciando extração de múltiplos stories...")
    print("   (Pressione Ctrl+C para parar)\n")

    try:
        while story_num < max_stories:
            print(f"📖 Story {story_num + 1}:")

            # 1. Clica em "Seen by" para abrir lista
            if not click_seen_by():
                print("   ⚠️  Não encontrou 'Seen by' - pode não ter viewers ou acabaram os stories")
                break

            time.sleep(1.5)  # Aguarda modal abrir

            # 2. Extrai todos os viewers com scroll
            viewers = extract_all_with_scroll(max_scrolls=15)

            story_data = {
                "story_index": story_num,
                "viewer_count": len(viewers),
                "extracted_at": datetime.now().isoformat(),
                "viewers": viewers
            }
            all_data["stories"].append(story_data)

            total_viewers += len(viewers)
            print(f"   ✅ {len(viewers)} viewers extraídos")

            # 3. Fecha modal de viewers
            close_viewers_modal()
            time.sleep(0.5)

            # 4. Verifica se há mais stories
            if not has_more_stories():
                print("\n   📍 Último story alcançado!")
                break

            # 5. Navega para próximo story
            navigate_to_next_story()
            story_num += 1
            time.sleep(2)  # Aguarda carregar

    except KeyboardInterrupt:
        print("\n\n⏹️  Extração interrompida pelo usuário")

    all_data["total_stories"] = len(all_data["stories"])
    all_data["total_viewers"] = total_viewers

    # Calcula viewers únicos
    all_usernames = []
    for story in all_data["stories"]:
        all_usernames.extend([v["username"] for v in story["viewers"]])
    all_data["unique_viewers"] = len(set(all_usernames))

    return all_data

def save_all_stories(data: dict) -> tuple:
    """Salva dados de múltiplos stories."""
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

    # JSON completo
    json_file = OUTPUT_DIR / f"all_stories_{timestamp}.json"
    with open(json_file, 'w', encoding='utf-8') as f:
        json.dump(data, f, indent=2, ensure_ascii=False)

    # CSV com todos os viewers
    csv_file = OUTPUT_DIR / f"all_viewers_{timestamp}.csv"
    with open(csv_file, 'w', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        writer.writerow(['story_index', 'username', 'name', 'profile_url', 'profile_pic'])

        for story in data["stories"]:
            for v in story["viewers"]:
                writer.writerow([
                    story["story_index"],
                    v.get("username", ""),
                    v.get("name", ""),
                    v.get("profile_url", ""),
                    v.get("profile_pic", "")
                ])

    return json_file, csv_file

def main():
    print("\n" + "="*60)
    print("📸 Instagram Story Viewers Extractor")
    print("="*60)

    # Verifica Safari
    if not check_safari_devtools():
        return

    # Verifica URL
    url = get_current_url()
    print(f"\n📍 URL: {url}")

    if "instagram.com" not in url:
        print("\n⚠️  Você não está no Instagram!")
        print("   Navegue até instagram.com e abra seu story.")
        return

    print("\n📋 Certifique-se que:")
    print("   1. Você está logado no Instagram")
    print("   2. Seu story está aberto")
    print("   3. A lista de viewers está visível")
    print("      (clique no ícone de visualizações)")

    input("\n   Pressione ENTER quando pronto...")

    print("\n🔍 Extraindo viewers...")

    # Primeira tentativa
    viewers = extract_story_viewers()

    # Fallback: parse do texto
    if not viewers:
        print("   Método 1 falhou, tentando via texto...")
        text = get_page_text()
        viewers = parse_viewers_from_text(text)

    if viewers:
        print(f"\n✅ Encontrados {len(viewers)} viewers!")
        print_viewers(viewers)

        # Menu de opções
        print("\n" + "-"*40)
        print("Opções:")
        print("  [1] Salvar dados")
        print("  [2] Extrair mais (scroll automático)")
        print("  [3] Modo monitor (novos viewers)")
        print("  [4] 🔄 EXTRAIR TODOS OS STORIES")
        print("  [5] Sair")

        choice = input("\nEscolha: ").strip()

        if choice == "1":
            json_file, csv_file = save_viewers(viewers)
            print(f"\n💾 Salvo!")
            print(f"   JSON: {json_file}")
            print(f"   CSV:  {csv_file}")

        elif choice == "2":
            all_viewers = extract_all_with_scroll()
            print(f"\n✅ Total extraído: {len(all_viewers)} viewers")
            print_viewers(all_viewers)

            save = input("\nSalvar? (s/n): ")
            if save.lower() == 's':
                json_file, csv_file = save_viewers(all_viewers)
                print(f"💾 Salvo em: {json_file}")

        elif choice == "3":
            interval = input("Intervalo em segundos (30): ").strip()
            interval = int(interval) if interval else 30
            monitor_mode(interval)

        elif choice == "4":
            # EXTRAIR TODOS OS STORIES
            print("\n" + "="*60)
            print("🔄 EXTRAÇÃO DE MÚLTIPLOS STORIES")
            print("="*60)
            print("\nIsso irá:")
            print("  1. Abrir lista de viewers do story atual")
            print("  2. Extrair todos os viewers com scroll")
            print("  3. Fechar a lista")
            print("  4. Navegar para o próximo story")
            print("  5. Repetir até acabar os stories")

            confirm = input("\nContinuar? (s/n): ").strip()
            if confirm.lower() == 's':
                all_data = extract_all_stories()

                print("\n" + "="*60)
                print("📊 RESUMO DA EXTRAÇÃO")
                print("="*60)
                print(f"   Stories processados: {all_data['total_stories']}")
                print(f"   Total de viewers: {all_data['total_viewers']}")
                print(f"   Viewers únicos: {all_data['unique_viewers']}")

                if all_data['stories']:
                    json_file, csv_file = save_all_stories(all_data)
                    print(f"\n💾 Dados salvos:")
                    print(f"   JSON: {json_file}")
                    print(f"   CSV:  {csv_file}")

    else:
        print("\n❌ Nenhum viewer encontrado.")
        print("\n   Possíveis soluções:")
        print("   1. Certifique-se que a lista de viewers está aberta")
        print("   2. Role a lista um pouco manualmente")
        print("   3. Aguarde a lista carregar completamente")

        retry = input("\n   Tentar novamente com scroll? (s/n): ")
        if retry.lower() == 's':
            all_viewers = extract_all_with_scroll()
            if all_viewers:
                print(f"\n✅ Encontrados {len(all_viewers)} viewers!")
                print_viewers(all_viewers)
                json_file, csv_file = save_viewers(all_viewers)
                print(f"\n💾 Salvo em: {json_file}")

if __name__ == "__main__":
    main()
