# /opt/docker/dev/service_finder/backend/app/workers/vehicle/ultimatespecs/vehicle_ultimate_r1_scraper.py
#!/usr/bin/env python3
"""
Worker: vehicle_ultimate_r1_scraper
Producer-Consumer lánc második eleme (A Nyers Letöltő).
Kivesz egy feldolgozandó linket a vehicle.auto_data_crawler_queue táblából (level='engine'),
letölti a HTML tartalmat Playwright böngészővel, kinyeri a specifikációkat JS parserrel,
és elmenti a vehicle.external_reference_library táblába.
"""

import asyncio
import logging
import random
import sys
import signal
import json
from datetime import datetime
from typing import Optional, Dict, Any, List

from playwright.async_api import async_playwright, Page, Browser, BrowserContext, TimeoutError as PlaywrightTimeoutError
from sqlalchemy import text, select, and_, or_
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import AsyncSessionLocal, ensure_models_loaded
from app.models.vehicle.external_reference_queue import ExternalReferenceQueue
from app.models.vehicle.external_reference import ExternalReferenceLibrary

# Logging konfiguráció
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [R1-SCRAPER] %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger("R1-SCRAPER")

# Konfiguráció
SLEEP_INTERVAL = random.uniform(3, 6)  # 3-6 mp között várakozás
MAX_RETRIES = 3
CLOUDFLARE_KEYWORDS = ["just a moment", "cloudflare", "checking your browser"]


class UltimateSpecsScraper:
    def __init__(self):
        self.running = True
        self.playwright = None
        self.browser: Optional[Browser] = None
        self.context: Optional[BrowserContext] = None
        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"
        )

    async def init_browser(self):
        """Playwright böngésző inicializálása"""
        try:
            self.playwright = await async_playwright().start()
            self.browser = await self.playwright.chromium.launch(
                headless=True,
                args=[
                    '--disable-blink-features=AutomationControlled',
                    '--disable-dev-shm-usage',
                    '--no-sandbox',
                ]
            )
            self.context = await self.browser.new_context(
                user_agent=self.user_agent,
                viewport={'width': 1920, 'height': 1080},
                java_script_enabled=True
            )
            logger.info("Playwright böngésző inicializálva")
        except Exception as e:
            logger.error(f"Hiba a böngésző inicializálásakor: {e}")
            raise

    async def close_browser(self):
        """Playwright böngésző lezárása"""
        if self.context:
            await self.context.close()
        if self.browser:
            await self.browser.close()
        if self.playwright:
            await self.playwright.stop()
        logger.info("Playwright böngésző lezárva")

    async def fetch_next_queue_item(self, session: AsyncSession) -> Optional[Dict[str, Any]]:
        """
        Kivesz egy feldolgozandó linket a vehicle.auto_data_crawler_queue táblából.
        """
        query = text("""
            SELECT id, url, category, parent_id 
            FROM vehicle.auto_data_crawler_queue 
            WHERE level = 'engine' AND status = 'pending' 
            FOR UPDATE SKIP LOCKED LIMIT 1
        """)
        
        try:
            result = await session.execute(query)
            row = result.fetchone()
            if row:
                return {
                    "id": row[0],
                    "url": row[1],
                    "category": row[2],
                    "parent_id": row[3]
                }
            return None
        except Exception as e:
            logger.error(f"Hiba a queue lekérdezésekor: {e}")
            return None

    async def scrape_with_retry(self, url: str, max_retries: int = MAX_RETRIES) -> Optional[Dict[str, Any]]:
        """
        Playwright böngészővel letölti a HTML tartalmat, retry logikával.
        """
        for attempt in range(1, max_retries + 1):
            try:
                logger.info(f"Próbálkozás {attempt}/{max_retries}: {url}")
                page = await self.context.new_page()
                
                # Navigáció
                await page.goto(url, wait_until="domcontentloaded", timeout=30000)
                
                # Várjunk a táblázatokra
                try:
                    await page.wait_for_selector('table', timeout=5000)
                except PlaywrightTimeoutError:
                    logger.warning("Nem található táblázat 5 másodpercen belül, de folytatjuk")
                
                # Ellenőrizzük Cloudflare blokkolást
                title = await page.title()
                title_lower = title.lower()
                if any(keyword in title_lower for keyword in CLOUDFLARE_KEYWORDS):
                    raise Exception(f"Cloudflare blokkolás észlelve: {title}")
                
                # JS parser futtatása
                specs = await page.evaluate("""() => {
                    let results = {};
                    // 1. ÖSSZES táblázat letapogatása
                    document.querySelectorAll('table').forEach(table => {
                        table.querySelectorAll('tr').forEach(row => {
                            let t = row.querySelector('.table_specs_title, .td_title, td:first-child, th:first-child');
                            let v = row.querySelector('.table_specs_value, .td_value, td:last-child');
                            if(t && v) {
                                let k = t.innerText.replace(/:/g,'').trim().toLowerCase();
                                let val = v.innerText.trim();
                                if(k && val && val !== "-") { results[k] = val; }
                            }
                        });
                    });
                    // 2. Extra szekciók és dimenziók mentése
                    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 t = row.querySelector('td:first-child, th:first-child');
                                        let v = row.querySelector('td:last-child');
                                        if(t && v) {
                                            let k = t.innerText.replace(/:/g,'').trim().toLowerCase();
                                            let val = v.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;
                }""")
                
                await page.close()
                
                if specs and len(specs) > 0:
                    logger.info(f"Sikeres letöltés, {len(specs)} specifikáció kinyerve")
                    return specs
                else:
                    logger.warning("Üres specifikációk, újrapróbálkozás")
                    raise Exception("Üres specifikációk")
                    
            except Exception as e:
                logger.error(f"Hiba a {attempt}. próbálkozásnál: {e}")
                if attempt < max_retries:
                    backoff = random.uniform(2, 5)
                    logger.info(f"Várakozás {backoff:.1f} másodpercet...")
                    await asyncio.sleep(backoff)
                else:
                    logger.error(f"Összes próbálkozás sikertelen: {e}")
                    return None
        
        return None

    async def process_queue_item(self, session: AsyncSession, item: Dict[str, Any]) -> bool:
        """
        Feldolgoz egy queue tételt: letölti, kinyeri, elmenti.
        """
        queue_id = item["id"]
        url = item["url"]
        category = item["category"]
        
        try:
            # 1. Letöltés
            specs = await self.scrape_with_retry(url)
            
            if not specs:
                # Hiba esetén frissítjük a queue-t
                await session.execute(
                    text("""
                        UPDATE vehicle.auto_data_crawler_queue
                        SET status = 'error', error_msg = :error_msg, retry_count = retry_count + 1
                        WHERE id = :id
                    """),
                    {"error_msg": "Sikertelen letöltés (üres specifikációk vagy Cloudflare)", "id": queue_id}
                )
                await session.commit()
                logger.error(f"Queue {queue_id} sikertelen, státusz: error")
                return False
            
            # 2. Új rekord létrehozása az external_reference_library táblában (nyers SQL)
            # A specifications dict-et JSON stringgé alakítjuk
            import json
            specs_json = json.dumps(specs)
            insert_query = text("""
                INSERT INTO vehicle.external_reference_library
                (source_name, source_url, category, specifications, pipeline_status, created_at, last_scraped_at)
                VALUES (:source_name, :source_url, :category, CAST(:specifications AS jsonb), :pipeline_status, NOW(), NOW())
                RETURNING id
            """)
            result = await session.execute(
                insert_query,
                {
                    "source_name": "ultimatespecs",
                    "source_url": url,
                    "category": category,
                    "specifications": specs_json,
                    "pipeline_status": "pending_enrich"
                }
            )
            new_id = result.scalar()
            
            # 3. Queue tétel frissítése completed-re
            await session.execute(
                text("""
                    UPDATE vehicle.auto_data_crawler_queue
                    SET status = 'completed', updated_at = NOW()
                    WHERE id = :id
                """),
                {"id": queue_id}
            )
            
            await session.commit()
            logger.info(f"Queue {queue_id} sikeresen feldolgozva, library ID: {new_id}")
            return True
            
        except Exception as e:
            logger.error(f"Hiba a queue {queue_id} feldolgozásakor: {e}")
            await session.rollback()
            
            # Hiba esetén error státusz
            try:
                await session.execute(
                    text("""
                        UPDATE vehicle.auto_data_crawler_queue
                        SET status = 'error', error_msg = :error_msg, retry_count = retry_count + 1
                        WHERE id = :id
                    """),
                    {"error_msg": str(e)[:500], "id": queue_id}
                )
                await session.commit()
            except Exception as update_err:
                logger.error(f"Hiba a queue frissítésekor: {update_err}")
            
            return False

    async def run_once(self):
        """Egyetlen feldolgozási ciklus"""
        # Biztosítjuk, hogy a modellek regisztrálva legyenek
        ensure_models_loaded()
        
        async with AsyncSessionLocal() as session:
            try:
                # Tranzakció kezdése
                async with session.begin():
                    item = await self.fetch_next_queue_item(session)
                    if not item:
                        logger.info("Nincs feldolgozandó queue tétel")
                        return False
                    
                    logger.info(f"Feldolgozás: {item['url']}")
                    success = await self.process_queue_item(session, item)
                    return success
                    
            except Exception as e:
                logger.error(f"Hiba a run_once-ban: {e}")
                return False

    async def run_loop(self):
        """Fő ciklus: végtelen while, 3-6 mp várakozással"""
        await self.init_browser()
        
        try:
            while self.running:
                success = await self.run_once()
                
                if not success:
                    # Ha nincs munka, várjunk egy kicsit
                    sleep_time = SLEEP_INTERVAL
                    logger.debug(f"Várakozás {sleep_time:.1f} másodpercet...")
                    await asyncio.sleep(sleep_time)
                else:
                    # Sikeres feldolgozás után rövid várakozás
                    await asyncio.sleep(random.uniform(1, 2))
                    
        except KeyboardInterrupt:
            logger.info("Keyboard interrupt, leállítás...")
        except Exception as e:
            logger.error(f"Váratlan hiba a fő ciklusban: {e}")
        finally:
            await self.close_browser()

    def stop(self):
        """Leállítási jelzés"""
        self.running = False
        logger.info("Leállítási jelzés küldve")


async def main():
    """Fő függvény"""
    scraper = UltimateSpecsScraper()
    
    # Signal kezelés
    def signal_handler(signum, frame):
        scraper.stop()
    
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    
    try:
        await scraper.run_loop()
    except Exception as e:
        logger.error(f"Fatal error: {e}")
        sys.exit(1)


if __name__ == "__main__":
    asyncio.run(main())