#!/usr/bin/env python3
import asyncio
import json
import logging
import random
import urllib.parse
import sys
import signal
import re
from datetime import datetime
from playwright.async_api import async_playwright
from sqlalchemy import text
from app.database import AsyncSessionLocal

# R2.3 - SENTINEL (Hardened, Drill-Up/Drill-Down & Omnivorous Parser Edition)
logging.basicConfig(level=logging.INFO, format='%(asctime)s [R2.3-SENTINEL] %(message)s')
logger = logging.getLogger("R2.3")

# --- 1. SZŰRÉSEK ÉS TILTÓLISTÁK ---
JUNK_LIST = [
    'SARIS', 'ANSSEMS', 'HAPERT', 'HUMBAUR', 'EDUARD', 'IFOR WILLIAMS', 'FENDT', 
    'HOBBY', 'ADRIA', 'PEECON', 'JAKO', 'KAWECO', 'POTTINGER', 'BOCKMANN',
    'JOHN DEERE', 'CLAAS', 'IVECO', 'SCANIA', 'MAN', 'DAF', 'KNAUS', 'PÖSSL', 
    'HYMER', 'WESTFALIA', 'AGM', 'BRENDERUP', 'STEMA', 'DEBON', 'TEMARED', 
    'MARTZ', 'NIEWIADOW', 'ZASLAW'
]

# --- 2. FORDÍTÁSOK ---
TRANSLATIONS = {
    "3ER REIHE": "3 Series", "5ER REIHE": "5 Series", "1ER REIHE": "1 Series", "7ER REIHE": "7 Series",
    "E-KLASSE": "E Class", "C-KLASSE": "C Class", "S-KLASSE": "S Class", "A-KLASSE": "A Class",
    "REIHE": "Series", "KLASSE": "Class", "BESTELWAGEN": "Van"
}

class RobotScout:
    def __init__(self):
        self.user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
        self.running = True

    def clean_name(self, make, model):
        """Standardizált angol név előállítása."""
        m = str(model).upper()
        for de, en in TRANSLATIONS.items():
            m = m.replace(de, en)
        m = m.replace(make.upper(), "").strip()
        return f"{make} {m}"

    # Rugalmas szótár (Fuzzy Match Keywords)
    FUZZY_MAPPING = {
        "power_kw": ["power", "horsepower", "output", "hp"],
        "engine_capacity": ["displacement", "capacity", "cm3", "cu-in"],
        "torque_nm": ["torque"],
        "max_speed": ["top speed", "maximum speed"],
        "curb_weight": ["curb weight", "weight"],
        "wheelbase": ["wheelbase"],
        "seats": ["seats", "num. of seats"]
    }

    def extract_fuzzy_metric(self, web_data: dict, keywords: list) -> str:
        """Megkeresi a JSON-ben azt az értéket, aminek a kulcsa tartalmazza valamelyik kulcsszót."""
        for key, val in web_data.items():
            k_lower = key.lower()
            for kw in keywords:
                if kw in k_lower:
                    return str(val)
        return ""

    def clean_number(self, val: str) -> int:
        """Kinyeri a nyers szövegből a releváns számot (okosan kezeli a kW-ot)."""
        if not val or val == "-" or val == "None": return 0
        try:
            val_lower = val.lower()
            if "kw" in val_lower:
                kw_match = re.search(r'(\d+)\s*kw', val_lower)
                if kw_match: return int(kw_match.group(1))
            
            nums = re.findall(r'\d+', val.replace(' ', '').replace(',', '').replace('.', ''))
            return int(nums[0]) if nums else 0
        except:
            return 0

    async def _retry_with_backoff(self, func, max_attempts=3, base_delay=2, exception_message="Retry failed"):
        """Újrapróbálkozási logika exponenciális késleltetéssel."""
        for attempt in range(max_attempts):
            try:
                return await func()
            except Exception as e:
                if attempt == max_attempts - 1:
                    logger.error(f"{exception_message} ({max_attempts}. kísérlet után is): {str(e)[:100]}")
                    raise
                else:
                    delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
                    logger.warning(f"⚠️ Próba {attempt + 1} sikertelen: {str(e)[:50]}. Újrapróbálkozás {delay:.1f}mp múlva...")
                    await asyncio.sleep(delay)
        return None

    async def get_car_links(self, page, make, model, year, use_year=True):
        """Intelligens kereső: Ha talál egy autót, felmegy a Generációhoz (Drill-Up), majd kigyűjti a variációkat (Drill-Down)."""
        clean_model = self.clean_name(make, model)
        search_query = f"{clean_model} {year}" if use_year else clean_model
        url = f"https://www.ultimatespecs.com/index.php?q={urllib.parse.quote(search_query)}"
        
        make_url_safe = str(make).replace(' ', '-').lower()
        model_keyword = str(model).strip().lower().split()[0] if str(model).strip() else ""
        
        # Ez a JavaScript funkció ki tudja nyerni egy adott oldalról az összes Specifikációt ÉS Generáció linket
        js_extractor = """
            (args) => {
                let targetMakeUrl = args.makeUrl;
                let targetModel = args.modelWord;
                let specs = [];
                let generations = [];
                let seenUrls = new Set();
                
                document.querySelectorAll('a').forEach(a => {
                    let href = a.getAttribute('href') || '';
                    let text = a.innerText.trim();
                    let hrefLow = href.toLowerCase();
                    let textLow = text.toLowerCase();
                    
                    if (hrefLow.includes('/car-specs/') || hrefLow.includes('/motorcycles-specs/')) {
                        // URL szintű Márka Szűrés!
                        if (hrefLow.includes('/' + targetMakeUrl + '/') || hrefLow.includes(targetMakeUrl + '-models')) {
                            // Modell Szűrés!
                            if (targetModel === '' || textLow.includes(targetModel) || hrefLow.includes(targetModel)) {
                                if (!seenUrls.has(href)) {
                                    seenUrls.add(href);
                                    if (hrefLow.endsWith('.html') && text.length > 1) {
                                        specs.push({ name: text, url: href });
                                    } else if (href.includes('/M') && href.split('/').length >= 4) {
                                        // UltimateSpecs generáció linkek (pl. /car-specs/Jeep/M14489/Grand-Cherokee)
                                        generations.push({ name: text, url: href });
                                    }
                                }
                            }
                        }
                    }
                });
                return { specs: specs, generations: generations };
            }
        """

        async def _fetch_links():
            logger.info(f"🔎 KERESÉS: {search_query}")
            await page.goto(url, wait_until="domcontentloaded", timeout=30000)
            
            data = await page.evaluate(js_extractor, {"makeUrl": make_url_safe, "modelWord": model_keyword})
            
            # --- 1. ESET: Direkt egy specifikus adatlapra irányított a kereső ---
            if page.url.endswith('.html') and f"/{make_url_safe}/" in page.url.lower():
                logger.info("🎯 Direkt találat! Lépjünk VISSZA 1 szintet a teljes kategóriáért (Drill-Up)...")
                if data['generations']:
                    gen_url = data['generations'][0]['url']
                    if not gen_url.startswith('http'): gen_url = "https://www.ultimatespecs.com" + gen_url
                    
                    logger.info(f"📂 Visszalépés ide: {gen_url}")
                    await page.goto(gen_url, wait_until="domcontentloaded", timeout=30000)
                    await asyncio.sleep(2)
                    
                    gen_data = await page.evaluate(js_extractor, {"makeUrl": make_url_safe, "modelWord": model_keyword})
                    return gen_data['specs']
                else:
                    return [{"name": await page.title(), "url": page.url}]

            # --- 2. ESET: Keresési találatok listáját kaptuk ---
            if data['specs']:
                first_spec_url = data['specs'][0]['url']
                if not first_spec_url.startswith('http'): first_spec_url = "https://www.ultimatespecs.com" + first_spec_url
                
                logger.info(f"🕵️ Találatok megvannak. Belépés az első autóba, hogy megtaláljuk a Generációját: {first_spec_url}")
                await page.goto(first_spec_url, wait_until="domcontentloaded", timeout=30000)
                await asyncio.sleep(2)
                
                spec_page_data = await page.evaluate(js_extractor, {"makeUrl": make_url_safe, "modelWord": model_keyword})
                
                if spec_page_data['generations']:
                    gen_url = spec_page_data['generations'][0]['url']
                    if not gen_url.startswith('http'): gen_url = "https://www.ultimatespecs.com" + gen_url
                    
                    logger.info(f"📂 Generáció megtalálva! Visszalépés, hogy leszüreteljük a teljes családot: {gen_url}")
                    await page.goto(gen_url, wait_until="domcontentloaded", timeout=30000)
                    await asyncio.sleep(2)
                    
                    final_data = await page.evaluate(js_extractor, {"makeUrl": make_url_safe, "modelWord": model_keyword})
                    if final_data['specs']:
                        return final_data['specs']
                
                # Ha valamiért nincs generációs link (nagyon ritka), adjuk vissza a keresési találatokat.
                return data['specs']

            # --- 3. ESET: Keresés azonnal egy Generációs oldalt dobott ki ---
            if not data['specs'] and data['generations']:
                gen_url = data['generations'][0]['url']
                if not gen_url.startswith('http'): gen_url = "https://www.ultimatespecs.com" + gen_url
                
                logger.info(f"📂 A keresés közvetlenül egy Kategóriát dobott ki. Belépés: {gen_url}")
                await page.goto(gen_url, wait_until="domcontentloaded", timeout=30000)
                await asyncio.sleep(2)
                final_data = await page.evaluate(js_extractor, {"makeUrl": make_url_safe, "modelWord": model_keyword})
                return final_data['specs']

            # Fallback évszám nélkül
            if not data['specs'] and use_year:
                logger.info("   ↳ Nincs találat évszámmal, próbálkozom évszám nélkül...")
                return await self.get_car_links(page, make, model, year, use_year=False)

            return data['specs']

        try:
            variants = await self._retry_with_backoff(
                _fetch_links,
                max_attempts=3,
                base_delay=2,
                exception_message=f"❌ Hálózati hiba a linkek keresésekor: {url}"
            )
            return variants if variants is not None else []
        except Exception as e:
            logger.error(f"❌ Keresési hiba (végleges): {str(e)[:50]}")
            return []

    async def scrape_car_details(self, page, url):
        """Mindenevő (Omnivorous) parser, ami minden táblázatot megeszik az oldalon."""
        async def _scrape():
            await page.goto(url, wait_until="networkidle", timeout=30000)
            
            full_specs = await page.evaluate("""
                () => {
                    let results = {};
                    
                    document.querySelectorAll('table').forEach(table => {
                        table.querySelectorAll('tr').forEach(row => {
                            let cells = row.querySelectorAll('td, th');
                            if(cells.length >= 2) {
                                let k = cells[0].innerText.replace(/:/g,'').trim().toLowerCase();
                                let v = cells[1].innerText.trim();
                                if(k && v && v !== "-") {
                                    results[k] = v;
                                }
                            }
                        });
                    });
                    
                    const sections = {};
                    document.querySelectorAll('h2, h3, h4, .section-title, .specs-header').forEach(header => {
                        const title = header.innerText.trim();
                        if (title && title.length > 0) {
                            let nextElement = header.nextElementSibling;
                            let sectionData = {};
                            for (let i = 0; i < 5 && nextElement; i++) {
                                if (nextElement.tagName === 'TABLE') {
                                    nextElement.querySelectorAll('tr').forEach(row => {
                                        let cells = row.querySelectorAll('td');
                                        if(cells.length >= 2) {
                                            let k = cells[0].innerText.replace(/:/g,'').trim().toLowerCase();
                                            let val = cells[1].innerText.trim();
                                            if(k && val && val !== "-") {
                                                sectionData[k] = val;
                                                results[`${title.toLowerCase().replace(/ /g, '_')}_${k}`] = val;
                                            }
                                        }
                                    });
                                }
                                nextElement = nextElement.nextElementSibling;
                            }
                            sections[title.toLowerCase().replace(/ /g, '_')] = sectionData;
                        }
                    });
                    
                    results['_sections'] = sections;
                    return results;
                }
            """)
            return full_specs

        try:
            logger.info(f"🌐 Scraping: {url}")
            full_specs = await self._retry_with_backoff(
                _scrape,
                max_attempts=3,
                base_delay=2,
                exception_message=f"❌ Scrape hiba az oldalon: {url}"
            )
            return full_specs
        except Exception as e:
            logger.error(f"❌ Scrape hiba (végleges): {str(e)[:100]}...")
            return None

    async def run(self):
        async with async_playwright() as p:
            browser = await p.chromium.launch(headless=True)
            context = await browser.new_context(user_agent=self.user_agent)
            page = await context.new_page()

            while self.running:
                wait = random.uniform(3, 6)
                logger.info(f"💤 Várakozás {wait:.1f} mp...")
                await asyncio.sleep(wait)

                async with AsyncSessionLocal() as db:
                    target = (await db.execute(text("""
                        SELECT id, make, marketing_name, year_from FROM vehicle.vehicle_model_definitions 
                        WHERE status IN ('pending', 'manual_review_needed') 
                        AND (vehicle_class IN ('car', 'motorcycle') OR vehicle_class IS NULL)
                        AND NOT (UPPER(make) = ANY(:junks))
                        ORDER BY priority_score DESC LIMIT 1
                    """), {"junks": JUNK_LIST})).fetchone()

                    if not target:
                        logger.info("✨ Minden tétel feldolgozva.")
                        break

                    t_id, make, model, year = target
                    logger.info(f"🚀 CÉLPONT: {make} {model} ({year}) [ID: {t_id}]")

                    try:
                        links = await self.get_car_links(page, make, model, year)
                    except Exception as e:
                        logger.error(f"❌ Hálózati hiba linkek lekérésekor: {str(e)[:100]}")
                        await db.execute(text("UPDATE vehicle.vehicle_model_definitions SET status='research_failed_network' WHERE id=:id"), {"id": t_id})
                        await db.commit()
                        continue
                    
                    if not links:
                        logger.warning(f"❌ Nem található adatlap a '{make} {model}' típushoz. research_failed_empty rögzítése.")
                        await db.execute(text("UPDATE vehicle.vehicle_model_definitions SET status='research_failed_empty', updated_at=NOW() WHERE id=:id"), {"id": t_id})
                        await db.commit()
                        continue

                    # --- 1. ELSŐ LINK DÚSÍTÁSA ---
                    first_link = links[0]
                    full_url = first_link['url'] if first_link['url'].startswith('http') else f"https://www.ultimatespecs.com{first_link['url']}"
                    logger.info(f"⚡ Azonnali adatgyűjtés a letöltött listából: {full_url}")
                    
                    web_data = await self.scrape_car_details(page, full_url)
                    is_enriched = False
                    
                    if web_data is None:
                        logger.error(f"❌ Scraping sikertelen minden próbálkozás után. research_failed_parsing rögzítése.")
                        await db.execute(text("UPDATE vehicle.vehicle_model_definitions SET status='research_failed_parsing' WHERE id=:id"), {"id": t_id})
                        await db.commit()
                        web_data = {} 
                        
                    elif len(web_data) >= 5:
                        updates = {}
                        for key, keywords in self.FUZZY_MAPPING.items():
                            raw_val = self.extract_fuzzy_metric(web_data, keywords)
                            updates[key] = self.clean_number(raw_val)

                        fuel_type = self.extract_fuzzy_metric(web_data, ["fuel type", "fuel"]) or 'Unknown'
                        transmission = self.extract_fuzzy_metric(web_data, ["transmission", "gearbox"]) or 'Unknown'
                        body_type = self.extract_fuzzy_metric(web_data, ["body", "type"]) or 'Unknown'
                        drive_type = self.extract_fuzzy_metric(web_data, ["drive", "traction"]) or 'Unknown'
                        
                        power_kw = updates.get('power_kw', 0)
                        ccm = updates.get('engine_capacity', 0)
                        
                        await db.execute(text("""
                            UPDATE vehicle.vehicle_model_definitions
                            SET power_kw = :power_kw, engine_capacity = :engine_capacity,
                                torque_nm = :torque_nm, max_speed = :max_speed,
                                curb_weight = :curb_weight,
                                wheelbase = :wheelbase, seats = :seats,
                                fuel_type = :fuel_type, transmission_type = :transmission_type,
                                drive_type = :drive_type, body_type = :body_type,
                                specifications = specifications || :full_json,
                                status = 'awaiting_ai_synthesis', updated_at = NOW()
                            WHERE id = :id
                        """), {
                            **updates,
                            "id": t_id,
                            "fuel_type": fuel_type,
                            "transmission_type": transmission,
                            "drive_type": drive_type,
                            "body_type": body_type,
                            "full_json": json.dumps(web_data)
                        })
                        is_enriched = True
                        logger.info(f"✅ SIKERES DÚSÍTÁS: {make} {model} ({power_kw} kW, {ccm} ccm) -> Awaiting AI")
                    else:
                        logger.warning("⚠️ Scraping kevés adatot talált, csak a linkeket mentjük.")
                    
                    # --- 2. VARIÁCIÓK MENTÉSE AZ R3-NAK ---
                    added = 0
                    for l in links:
                        v_url = l['url'] if l['url'].startswith('http') else f"https://www.ultimatespecs.com{l['url']}"
                        
                        check = (await db.execute(text("SELECT id FROM vehicle.vehicle_model_definitions WHERE raw_api_data->>'url' = :u"), {"u": v_url})).fetchone()
                        
                        if not check:
                            normalized = l['name'].lower().replace(' ', '_').replace('-', '_').replace('.', '').replace(',', '')[:200]
                            await db.execute(text("""
                                INSERT INTO vehicle.vehicle_model_definitions
                                (make, marketing_name, normalized_name, year_from, status,
                                 raw_api_data, priority_score, source, market,
                                 technical_code, variant_code, version_code,
                                 specifications, marketing_name_aliases, raw_search_context)
                                VALUES (:make, :name, :normalized, :year, 'awaiting_ai_synthesis',
                                        :raw, 30, 'ultimatespecs', 'EU',
                                        'UNKNOWN', 'UNKNOWN', 'UNKNOWN',
                                        '{}'::jsonb, '[]'::jsonb, '')
                            """), {
                                "make": make, "name": l['name'], "normalized": normalized,
                                "year": year, "raw": json.dumps({"url": v_url})
                            })
                            added += 1
                    
                    if not is_enriched:
                        await db.execute(text("UPDATE vehicle.vehicle_model_definitions SET status='expanded_to_variants', updated_at=NOW() WHERE id=:id"), {"id": t_id})
                    
                    await db.commit()
                    logger.info(f"✅ Variációk kezelve: {added} új rekord.")

            await browser.close()

if __name__ == "__main__":
    scout = RobotScout()
    def stop_signal(sig, frame):
        logger.info("🛑 LEÁLLÍTÁS (Kérés érzékelve)...")
        scout.running = False
        sys.exit(0)
    
    signal.signal(signal.SIGINT, stop_signal)
    
    try:
        asyncio.run(scout.run())
    except KeyboardInterrupt:
        pass