# /opt/docker/dev/service_finder/backend/app/workers/vehicle/ultimatespecs/vehicle_ultimate_r3_finalizer.py
#!/usr/bin/env python3
"""
Worker: vehicle_ultimate_r3_finalizer
Producer-Consumer lánc negyedik, utolsó eleme (Az Összevezető).
Offline dolgozik egy végtelen while ciklusban (1-3 mp delay), és a meglévő adatbázis-táblákat szinkronizálja.

1. Lekérdezés (JOIN a Queue-val): Kivesz egy `pending_match` sort a Library-ből, és a Queue-ból lekéri az eredeti `parent_id`-t és a link nevét.
2. Szülő (Base VMD) ellenőrzése: Lekérdezi az eredeti szülő rekordot a VMD táblából a parent_id alapján.
3. Összevezetés (UPDATE vagy INSERT): A letisztított adatok a lib.specifications['standardized'] dict-ből jönnek.
   - A ÁG: Ha a szülő status értéke IN ('pending', 'manual_review_needed'): UPDATE a szülő (VMD) rekordon
   - B ÁG: Ha a szülő status MÁR NEM 'pending': INSERT új variációként a VMD táblába
4. Library lezárása: Frissíti a Library táblát pipeline_status = 'completed', matched_vmd_id beállítása.
"""

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

from sqlalchemy import text, select, and_, or_
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import AsyncSessionLocal

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

# Konfiguráció
SLEEP_INTERVAL = random.uniform(1, 3)  # 1-3 mp között várakozás


class UltimateSpecsFinalizer:
    def __init__(self):
        self.running = True

    async def fetch_pending_match(self, session: AsyncSession) -> Optional[Dict[str, Any]]:
        """
        Kivesz egy `pending_match` sort a Library-ből, JOIN-olva a Queue-val.
        FOR UPDATE OF lib SKIP LOCKED LIMIT 1
        """
        query = text("""
            SELECT lib.id, lib.source_url, lib.make, lib.model, lib.year_from, 
                   lib.power_kw, lib.engine_cc, lib.specifications, lib.category,
                   q.parent_id, q.name AS variant_name
            FROM vehicle.external_reference_library lib
            JOIN vehicle.auto_data_crawler_queue q ON lib.source_url = q.url
            WHERE lib.pipeline_status = 'pending_match'
            FOR UPDATE OF lib SKIP LOCKED LIMIT 1
        """)
        result = await session.execute(query)
        row = result.fetchone()
        if not row:
            return None
        
        return {
            "lib_id": row[0],
            "source_url": row[1],
            "make": row[2],
            "model": row[3],
            "year_from": row[4],
            "power_kw": row[5],
            "engine_cc": row[6],
            "specifications": row[7] if row[7] else {},
            "category": row[8],
            "parent_id": row[9],
            "variant_name": row[10]
        }

    async def get_parent_vmd(self, session: AsyncSession, parent_id: int) -> Optional[Dict[str, Any]]:
        """
        Lekérdezi az eredeti szülő rekordot a VMD táblából a parent_id alapján.
        FOR UPDATE (zárolás a konkurrens feldolgozás elkerülésére)
        """
        query = text("""
            SELECT id, status FROM vehicle.vehicle_model_definitions 
            WHERE id = :parent_id FOR UPDATE
        """)
        result = await session.execute(query, {"parent_id": parent_id})
        row = result.fetchone()
        if not row:
            return None
        
        return {
            "id": row[0],
            "status": row[1]
        }

    def extract_standardized_data(self, specifications: Dict[str, Any]) -> Dict[str, Any]:
        """
        Kinyeri a standardizált adatokat a specifications['standardized'] dict-ből.
        Csonkolja a szöveges mezőket a VMD tábla korlátaihoz (50 karakter).
        """
        standardized = specifications.get('standardized', {})
        
        # Alapvető numerikus mezők
        extracted = {
            "power_kw": standardized.get("power_kw"),
            "engine_capacity": standardized.get("engine_capacity"),
            "torque_nm": standardized.get("torque_nm"),
            "max_speed": standardized.get("max_speed"),
            "curb_weight": standardized.get("curb_weight"),
            "wheelbase": standardized.get("wheelbase"),
            "seats": standardized.get("seats"),
            "fuel_type": standardized.get("fuel_type"),
            "transmission_type": standardized.get("transmission_type"),
            "drive_type": standardized.get("drive_type"),
            "body_type": standardized.get("body_type"),
        }
        
        # Csonkolás a VMD mezőhosszokhoz
        def truncate(value: Any, max_len: int = 50) -> Any:
            if isinstance(value, str) and len(value) > max_len:
                return value[:max_len]
            return value
        
        # Alkalmazza a csonkolást a szöveges mezőkre
        for field in ["fuel_type", "transmission_type", "drive_type", "body_type"]:
            if extracted.get(field):
                extracted[field] = truncate(extracted[field], 50)
        
        # Tisztítás: None értékek eltávolítása
        return {k: v for k, v in extracted.items() if v is not None}

    async def update_parent_vmd(self, session: AsyncSession, parent_id: int, 
                                lib_data: Dict[str, Any], standardized: Dict[str, Any]) -> int:
        """
        A ÁG: Frissíti a szülő VMD rekordot a kinyert standardizált adatokkal.
        Állítja a VMD status-át 'awaiting_ai_synthesis'-re.
        Visszaadja a parent_id-t (matched_vmd_id).
        """
        # Build update fields
        update_fields = {
            "power_kw": standardized.get("power_kw") or lib_data.get("power_kw"),
            "engine_capacity": standardized.get("engine_capacity") or lib_data.get("engine_cc"),
            "torque_nm": standardized.get("torque_nm"),
            "max_speed": standardized.get("max_speed"),
            "curb_weight": standardized.get("curb_weight"),
            "wheelbase": standardized.get("wheelbase"),
            "seats": standardized.get("seats"),
            "fuel_type": standardized.get("fuel_type"),
            "transmission_type": standardized.get("transmission_type"),
            "drive_type": standardized.get("drive_type"),
            "body_type": standardized.get("body_type"),
            "status": "awaiting_ai_synthesis",
            "updated_at": datetime.utcnow(),
            "source": "ultimatespecs",
            "priority_score": 30,
        }
        
        # Remove None values
        update_fields = {k: v for k, v in update_fields.items() if v is not None}
        
        # Build SET clause
        set_clause = ", ".join([f"{k} = :{k}" for k in update_fields.keys()])
        
        query = text(f"""
            UPDATE vehicle.vehicle_model_definitions
            SET {set_clause}
            WHERE id = :parent_id
            RETURNING id
        """)
        
        params = {"parent_id": parent_id, **update_fields}
        result = await session.execute(query, params)
        updated_id = result.scalar()
        
        logger.info(f"UPDATE parent VMD {parent_id} with {len(update_fields)} fields")
        return updated_id

    async def insert_variant_vmd(self, session: AsyncSession, lib_data: Dict[str, Any],
                                 standardized: Dict[str, Any], variant_name: str) -> int:
        """
        B ÁG: Beszúr egy új variációt a VMD táblába.
        make = lib.make, marketing_name = variant_name, year_from = lib.year_from.
        status = 'awaiting_ai_synthesis', source = 'ultimatespecs', priority_score = 30.
        Visszaadja az új ID-t (matched_vmd_id).
        Ha már létezik a rekord (duplicate key), visszaadja a meglévő ID-t.
        """
        # Build insert data
        insert_data = {
            "make": lib_data["make"],
            "marketing_name": variant_name,
            "official_marketing_name": variant_name,
            "year_from": lib_data["year_from"],
            "power_kw": standardized.get("power_kw") or lib_data.get("power_kw"),
            "engine_capacity": standardized.get("engine_capacity") or lib_data.get("engine_cc"),
            "torque_nm": standardized.get("torque_nm"),
            "max_speed": standardized.get("max_speed"),
            "curb_weight": standardized.get("curb_weight"),
            "wheelbase": standardized.get("wheelbase"),
            "seats": standardized.get("seats"),
            "fuel_type": standardized.get("fuel_type"),
            "transmission_type": standardized.get("transmission_type"),
            "drive_type": standardized.get("drive_type"),
            "body_type": standardized.get("body_type"),
            "status": "awaiting_ai_synthesis",
            "vehicle_class": lib_data.get("category"),
            "source": "ultimatespecs",
            "priority_score": 30,
            "created_at": datetime.utcnow(),
            "updated_at": datetime.utcnow(),
            "market": "EU",
            "normalized_name": f"{lib_data['make']} {variant_name}",
            "technical_code": "UNKNOWN",
            "variant_code": "UNKNOWN",
            "version_code": "UNKNOWN",
            "specifications": json.dumps({}),  # Üres JSON, mert NOT NULL
            "raw_api_data": json.dumps({}),    # Üres JSON
            "research_metadata": json.dumps({}), # Üres JSON
            "raw_search_context": "", # Üres string
        }
        
        # Remove None values
        insert_data = {k: v for k, v in insert_data.items() if v is not None}
        
        # Build columns and values
        columns = ", ".join(insert_data.keys())
        placeholders = ", ".join([f":{k}" for k in insert_data.keys()])
        
        try:
            # Próbáljuk meg beszúrni
            query = text(f"""
                INSERT INTO vehicle.vehicle_model_definitions ({columns})
                VALUES ({placeholders})
                RETURNING id
            """)
            
            result = await session.execute(query, insert_data)
            new_id = result.scalar()
            
            logger.info(f"INSERT new variant VMD {new_id} for {lib_data['make']} {variant_name}")
            return new_id
            
        except IntegrityError as e:
            # Duplicate key violation - rollback és új lekérdezés
            logger.warning(f"Duplicate key violation for {lib_data['make']} {variant_name}: {e}. Rolling back and looking for existing record...")
            
            # Rollback a megszakított tranzakciót
            await session.rollback()
            
            # Keresés a meglévő rekordra új tranzakcióban
            find_query = text("""
                SELECT id FROM vehicle.vehicle_model_definitions
                WHERE make = :make
                  AND marketing_name = :marketing_name
                  AND year_from = :year_from
                LIMIT 1
            """)
            
            find_params = {
                "make": lib_data["make"],
                "marketing_name": variant_name,
                "year_from": lib_data["year_from"]
            }
            
            result = await session.execute(find_query, find_params)
            existing_id = result.scalar()
            
            if existing_id:
                logger.info(f"Found existing VMD {existing_id} for {lib_data['make']} {variant_name}")
                return existing_id
            else:
                # Ha nem találjuk, dobjuk tovább a hibát
                logger.error(f"Duplicate key but could not find existing record for {lib_data['make']} {variant_name}")
                raise

    async def close_library_entry(self, session: AsyncSession, lib_id: int, matched_vmd_id: int):
        """
        Frissíti a Library táblát: pipeline_status = 'completed', matched_vmd_id beállítása.
        """
        query = text("""
            UPDATE vehicle.external_reference_library
            SET pipeline_status = 'completed',
                matched_vmd_id = :matched_vmd_id
            WHERE id = :lib_id
        """)
        await session.execute(query, {"lib_id": lib_id, "matched_vmd_id": matched_vmd_id})
        logger.info(f"Library {lib_id} closed with matched_vmd_id {matched_vmd_id}")

    async def process_one(self):
        """
        Feldolgoz egyetlen pending_match rekordot.
        """
        async with AsyncSessionLocal() as session:
            try:
                # 1. Lekérdezés a Library-ből
                lib_data = await self.fetch_pending_match(session)
                if not lib_data:
                    return False
                
                logger.info(f"Processing library ID {lib_data['lib_id']} for {lib_data['make']} {lib_data['model']}")
                
                # 2. Szülő VMD ellenőrzése
                parent_vmd = None
                if lib_data['parent_id']:
                    parent_vmd = await self.get_parent_vmd(session, lib_data['parent_id'])
                
                # 3. Standardizált adatok kinyerése
                standardized = self.extract_standardized_data(lib_data['specifications'])
                
                # 4. Döntés: UPDATE vagy INSERT
                matched_vmd_id = None
                
                if parent_vmd and parent_vmd['status'] in ('pending', 'manual_review_needed'):
                    # A ÁG: Szülő frissítése
                    matched_vmd_id = await self.update_parent_vmd(
                        session, parent_vmd['id'], lib_data, standardized
                    )
                else:
                    # B ÁG: Új variáció beszúrása
                    matched_vmd_id = await self.insert_variant_vmd(
                        session, lib_data, standardized, lib_data['variant_name']
                    )
                
                # 5. Library lezárása
                await self.close_library_entry(session, lib_data['lib_id'], matched_vmd_id)
                
                # Commit
                await session.commit()
                logger.info(f"Successfully finalized library {lib_data['lib_id']} -> VMD {matched_vmd_id}")
                return True
                
            except Exception as e:
                await session.rollback()
                logger.error(f"Error processing library {lib_data.get('lib_id', 'unknown')}: {e}")
                return False

    async def run(self, max_iterations: int = 10):
        """
        Fő futási ciklus: korlátozott számú iteráció, 1-3 mp várakozással.
        
        Args:
            max_iterations: Maximum number of processing cycles (default: 10)
        """
        logger.info(f"R3 Finalizer started. Max iterations: {max_iterations}. Waiting for pending_match entries...")
        
        iteration = 0
        while self.running and iteration < max_iterations:
            try:
                processed = await self.process_one()
                if not processed:
                    # Nincs munka vagy hiba történt, várakozás
                    await asyncio.sleep(SLEEP_INTERVAL)
                else:
                    # Sikeres feldolgozás után rövid várakozás
                    await asyncio.sleep(0.5)
                
                # Minden esetben növeljük az iterációt (akár sikeres, akár sikertelen volt)
                iteration += 1
                logger.info(f"Iteration {iteration}/{max_iterations} completed.")
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Unexpected error in main loop: {e}")
                await asyncio.sleep(5)
                # Hiba esetén is növeljük az iterációt
                iteration += 1
                logger.info(f"Iteration {iteration}/{max_iterations} completed after error.")
        
        logger.info(f"R3 Finalizer completed {iteration} iterations. Stopping.")
        self.stop()

    def stop(self):
        self.running = False
        logger.info("R3 Finalizer stopping...")


def main():
    # Signal kezelés
    finalizer = UltimateSpecsFinalizer()
    
    def signal_handler(signum, frame):
        logger.info(f"Received signal {signum}, shutting down...")
        finalizer.stop()
    
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    
    # Fő ciklus indítása - korlátozott számú iterációval teszteléshez
    try:
        # Teszteléshez: maximum 5 iteráció
        asyncio.run(finalizer.run(max_iterations=sys.maxsize))
    except KeyboardInterrupt:
        logger.info("Keyboard interrupt received, shutting down...")
    finally:
        logger.info("R3 Finalizer stopped.")


if __name__ == "__main__":
    main()