import os
import json
import random
import requests
import asyncio
from datetime import datetime
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig, CacheMode, BrowserConfig
from crawl4ai.async_configs import ProxyConfig
from crawl4ai.extraction_strategy import LLMExtractionStrategy
try:
    from crawl4ai import LLMConfig
except ImportError:
    from crawl4ai.extraction_strategy import LLMConfig

# ==============================================================================
# CONFIGURACION MAESTRA (ATLAS WAKEUP)
# ==============================================================================
os.environ["OPENAI_API_KEY"] = "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"

PROXY_URL = "http://uikPBRbczMQd8rBr:tuaE95tFQqa74nbO@geo.iproyal.com:12321"
N8N_WEBHOOK_URL = "https://n8n.orozdesign.net/webhook/crawl4ai-inbound"

NOCODB_API_URL = "https://db.orozdesign.net/api/v1/db/data/v1/p1rc5rxqribu59g"
NOCODB_TOKEN = "nc_pat_Tdip5x_XqJUoRkYAe7YBYhxDv4V1m7zM5iNQsAGz"

# NESTOR: El script ya conectó automáticamente a tu tabla "cities".
NOCODB_TABLE_CIUDADES = "mfdruari9ejzwrp" 

BASE_NICHE = "Cleaning Services"
NICHES = [
    "Cleaning Services",
    "Maid Services",
    "Housekeeping",
    "Janitorial Services"
]

# Colores de fallback por nicho según protocolo
FALLBACK_COLORS = {
    "Cleaning Services": {"color_1": "#07162d", "color_2": "#38bdf8"},
    "Landscaping": {"color_1": "#1a3a1a", "color_2": "#84cc16"},
    "Roofing": {"color_1": "#1c1917", "color_2": "#f97316"},
    "Construction": {"color_1": "#1e293b", "color_2": "#f59e0b"}
}

import re

def slugify(text):
    if not text: return ""
    return re.sub(r'[^a-z0-9]+', '-', text.lower()).strip('-')

def get_headers():
    return {"xc-token": NOCODB_TOKEN, "Content-Type": "application/json"}

def get_next_city():
    """Busca la primera ciudad en estado Pending en NocoDB"""
    if not NOCODB_TABLE_CIUDADES:
        return {"id": 0, "city": "Miami", "state": "FL"}
        
    url = f"{NOCODB_API_URL}/{NOCODB_TABLE_CIUDADES}?where=(mining_status,eq,Pending)&limit=1"
    try:
        res = requests.get(url, headers=get_headers())
        if res.status_code == 200:
            data = res.json()
            if data.get('list'):
                return data['list'][0]
        else:
            print(f"[!] Error de NocoDB ({res.status_code}): {res.text}")
    except Exception as e:
        print(f"[!] Error conectando a NocoDB: {e}")
    return None

def update_city_status(city_id, status):
    """Actualiza el estado de la ciudad en NocoDB (Processing / Completed)"""
    if not NOCODB_TABLE_CIUDADES or city_id == 0: return
    
    url = f"{NOCODB_API_URL}/{NOCODB_TABLE_CIUDADES}/{city_id}"
    data = {"mining_status": status, "last_mined": datetime.utcnow().isoformat()[:19].replace('T', ' ')}
    try:
        res = requests.patch(url, headers=get_headers(), json=data)
        if res.status_code not in (200, 204):
            print(f"[!] Error actualizando estado en NocoDB ({res.status_code}): {res.text}")
    except Exception as e:
        print(f"[!] Excepción actualizando NocoDB: {e}")

def send_to_n8n(lead_data, current_category):
    """Envía el lead estructurado al Webhook de recepción de n8n (Floox 01)"""
    try:
        # Aseguramos que vayan los colores base si la IA no los encontró
        if not lead_data.get("color_1"):
            c = FALLBACK_COLORS.get(BASE_NICHE, {"color_1": "#07162d", "color_2": "#f4a261"})
            lead_data["color_1"] = c["color_1"]
            lead_data["color_2"] = c["color_2"]

        # Formato exacto que espera tu WF1 y NocoDB
        payload = {
            "business_name": lead_data.get("business_name"),
            "phone": lead_data.get("phone"),
            "email": lead_data.get("email", ""),
            "city": lead_data.get("city"),
            "state": lead_data.get("state", ""),
            "website": lead_data.get("website", ""),
            "gmb_url": lead_data.get("gmb_url", ""),
            "category": current_category,
            "color_1": lead_data.get("color_1"),
            "color_2": lead_data.get("color_2"),
            "logo_url": lead_data.get("logo_url", ""),
            "slug": slugify(lead_data.get("business_name", ""))
        }
        headers = {"x-floox-key": "OrozDesign-Atlas-Team!"}
        response = requests.post(N8N_WEBHOOK_URL, json=payload, headers=headers)
        if response.status_code == 200:
            print(f"       [OK] Lead '{payload['business_name']}' inyectado a n8n.")
        else:
            print(f"       [FAIL] Error {response.status_code} al enviar a n8n.")
    except Exception as e:
        print(f"       [ERROR] Falló la conexión con n8n: {e}")

async def extract_google_maps_leads(city_data):
    city_name = city_data.get('city', '')
    state = city_data.get('state', '')
    
    for variant in NICHES:
        search_query = f"{variant} in {city_name}, {state}"
        print(f"\n[*] INICIANDO EXTRACCIÓN: {search_query}")
        print(f"[*] Proxy Seguro Activo: IPRoyal")

        # Estrategia de IA: Aplica el filtro estricto de Hispanidad
        instruction = (
            "Extract local businesses. YOU ARE STRICTLY FORBIDDEN FROM EXTRACTING BUSINESSES THAT DO NOT MATCH THIS FILTER:\n"
            "1. Must have a visible phone number.\n"
            "2. HISPANIC FILTER: The business name MUST contain a clear Latino surname (e.g., Garcia, Lopez, Ramirez, Martinez, Rodriguez, Hernandez). "
            "If the business name is completely in English or does not contain a Latino surname (e.g., 'Clean Image', 'The Valley Clean Team', 'Grissom Cleaners'), YOU MUST COMPLETELY IGNORE IT.\n\n"
            "Return a JSON list. Each object must have exactly these keys: business_name (string), phone (string), email (string or empty), "
            "city (string), state (abbreviated string, e.g., FL), gmb_url (string google maps url), "
            "website (string URL or empty), color_1 (hex or empty), color_2 (hex or empty), logo_url (string or empty), "
            "hispanic_indicator (string: state the exact Latino surname found in the name)."
        )
        
        extraction_strategy = LLMExtractionStrategy(
            llm_config=LLMConfig(provider="openai/gpt-4o-mini"),
            instruction=instruction
        )

        # PROTECCIÓN ACTIVA: IP del VPS autorizada en IPRoyal. Bypass total de usuario y contraseña.
        proxy = ProxyConfig(
            server="http://geo.iproyal.com:12321"
        )
        browser_config = BrowserConfig(proxy_config=proxy, headless=True)
        
        delay_seconds = round(random.uniform(7.0, 12.0), 2)
        print(f"[*] Pausa natural anti-bot: {delay_seconds}s")
        
        config = CrawlerRunConfig(
            cache_mode=CacheMode.BYPASS, 
            magic=True, # Activa el modo anti-bot y stealth de crawl4ai para que Google no bloquee al proxy
            extraction_strategy=extraction_strategy,
            delay_before_return_html=delay_seconds # Anti-Fingerprint delay y tiempo para que carguen los resultados
        )

        target_url = f"https://www.google.com/maps/search/{search_query.replace(' ', '+')}"

        async with AsyncWebCrawler(config=browser_config) as crawler:
            result = await crawler.arun(url=target_url, config=config)

            if not result.success:
                print(f"[!] Error en extracción para {variant}. Posible bloqueo o timeout:", result.error_message)
                continue

            try:
                leads = json.loads(result.extracted_content)
                print(f"[+] ÉXITO: Se extrajeron {len(leads)} leads latinos puros para {variant}.")
                
                for lead in leads:
                    # Usamos la ciudad y estado del query original SOLO como último recurso
                    if not lead.get("city"):
                        lead["city"] = city_name
                    if not lead.get("state"):
                        lead["state"] = state
                    send_to_n8n(lead, variant)
                    
            except Exception as e:
                print(f"[!] Error estructurando los datos de la IA para {variant}:", e)

def check_n8n_status():
    """Verifica si el workflow de n8n está encendido (Active)."""
    try:
        # Hacemos un ping sin la llave de seguridad.
        # Si n8n está ENCENDIDO, el workflow nos rechazará con un 403 (Forbidden) o 400.
        # Si n8n está APAGADO, el servidor de n8n nos devolverá un 404 (Not Found).
        res = requests.post(N8N_WEBHOOK_URL, json={"ping": True}, timeout=10)
        if res.status_code == 404:
            return False
        return True
    except Exception as e:
        print(f"[!] Error conectando a n8n para validación: {e}")
        return False

if __name__ == "__main__":
    print("========================================")
    print(" ATLAS RECOLECTOR - INICIANDO MINERO v1.0   ")
    print("========================================")
    
    # 1. EL JEFE MANDA: Verificamos si n8n está encendido antes de hacer cualquier cosa.
    if not check_n8n_status():
        print("[-] ABORTADO: n8n está APAGADO. El sistema de recolección se detiene por órdenes del jefe.")
        exit(0)
        
    print("[+] n8n está ONLINE y escuchando. Permiso concedido para minar.")

    # Asegúrate de tener la variable de entorno OPENAI_API_KEY en tu servidor
    if not os.environ.get("OPENAI_API_KEY"):
        print("[!] ATENCIÓN: No tienes la variable OPENAI_API_KEY configurada. El LLM podría fallar.")

    city = get_next_city()
    if city:
        city_id = city.get('Id', city.get('id', 0))
        update_city_status(city_id, "Processing")
        asyncio.run(extract_google_maps_leads(city))
        update_city_status(city_id, "Completed")
        print("\n[+] Ciclo finalizado exitosamente. Minero apagándose hasta el próximo llamado.")
    else:
        print("[*] No hay ciudades en estado 'Pending'. El minero descansará.")
