"""QWEN 3.6 Fitment Service — Vehicle compatibility lookup via AI.

Uses a private QWEN server (OpenAI-compatible API) to find compatible
vehicles for a given part number + name + brand, then validates them
against the master database (model_year_engine).
"""
import json
import re
import time
import requests
from config import QWEN_API_URL, QWEN_API_KEY, QWEN_MODEL


def get_vehicle_fitment(part_number, name, brand):
    """Ask QWEN for compatible vehicles and validate against master DB.

    Returns: {
        'vehicles': [{'make': 'Toyota', 'model': 'Corolla', 'year': 2015, 'engine': '1.8L', 'mye_id': 123}],
        'confidence': 0.92,
        'notes': ''
    }
    """
    if not QWEN_API_URL or not QWEN_API_KEY:
        return {'vehicles': [], 'confidence': 0, 'notes': 'QWEN not configured'}

    prompt = _build_prompt(part_number, name, brand)
    content = ''
    last_error = None

    for attempt in range(3):
        try:
            response = requests.post(
                f"{QWEN_API_URL}/chat/completions",
                headers={
                    'Authorization': f'Bearer {QWEN_API_KEY}',
                    'Content-Type': 'application/json',
                },
                json={
                    'model': QWEN_MODEL,
                    'messages': [
                        {'role': 'system', 'content': 'Eres un experto en autopartes mexicanas y del mercado aftermarket norteamericano. Devuelve SIEMPRE JSON valido sin markdown.'},
                        {'role': 'user', 'content': prompt}
                    ],
                    'temperature': 0.2,
                    'max_tokens': 8192,
                },
                timeout=120,
            )
            response.raise_for_status()
            raw = response.json()
            finish_reason = None
            if raw.get('choices') and len(raw['choices']) > 0:
                choice = raw['choices'][0]
                msg = choice.get('message', {})
                finish_reason = choice.get('finish_reason')
                if msg:
                    content = msg.get('content') or ''
                    if not content:
                        # Fallback for reasoning models that return output in reasoning_content
                        content = msg.get('reasoning_content') or ''
            if content:
                break
        except requests.RequestException as exc:
            last_error = exc
            # Retry on request failure
        if attempt < 2:
            time.sleep(1)

    if not content:
        err_msg = f'QWEN request failed: {last_error}' if last_error else 'Empty response from QWEN after 3 attempts'
        if finish_reason == 'length':
            err_msg += ' (response truncated by token limit — consider reducing prompt or increasing max_tokens)'
        return {'vehicles': [], 'confidence': 0, 'notes': err_msg}

    # Parse JSON from QWEN response (sometimes wrapped in markdown)
    parsed = _extract_json(content)
    if not parsed:
        return {'vehicles': [], 'confidence': 0, 'notes': 'Invalid JSON from QWEN: ' + str(content)[:200]}

    # Extract vehicles list from various possible structures
    vehicles_raw = _extract_vehicles(parsed)
    confidence = parsed.get('confidence', 0) or parsed.get('confianza', 0)
    notes = parsed.get('notes', '') or parsed.get('notas', '') or parsed.get('advertencia', '')

    # Validate vehicles against master DB
    validated = _validate_vehicles(vehicles_raw)

    return {
        'vehicles': validated,
        'confidence': confidence,
        'notes': notes,
    }


def _build_prompt(part_number, name, brand):
    brand_str = brand or 'desconocida'
    return f"""Dado el siguiente repuesto automotriz para el mercado mexicano y aftermarket norteamericano:
- Numero de parte: {part_number}
- Nombre/descripcion: {name}
- Marca del fabricante: {brand_str}

Devuelve UNICAMENTE un JSON valido (sin markdown, sin backticks, sin texto adicional) con esta estructura:
{{"vehicles":[{{"make":"Toyota","model":"Corolla","year_range":"2014-2019","engine":"1.8L","engine_code":"2ZR-FE","notes":""}}],"confidence":0.92,"notes":""}}

REGLAS OBLIGATORIAS:
1. "make" = marca del vehiculo.
2. "model" = modelo exacto (incluye variante si aplica).
3. USA "year_range" = string "YYYY-YYYY" cuando el MISMO modelo/motor abarca multiples anos consecutivos. Esto ahorra tokens y permite mas resultados.
4. USA "year" = int SOLO cuando sea un ano aislado sin rangos adyacentes.
5. "engine" = descripcion corta del motor (ej: "1.8L", "V6 3.5L"). Si no lo conoces, usa "".
6. "engine_code" = codigo exacto SI LO CONOCES. Si no, usa "".
7. "notes" = string vacio "" para ahorrar tokens, salvo que haya una advertencia critica.
8. Devuelve TODOS los vehiculos compatibles que conozcas. Minimo 1, maximo 200. Para piezas genericas (filtros de aceite, bujias, balatas, amortiguadores) incluye TODOS los modelos aplicables.
9. "confidence" entre 0.0 y 1.0. Valores >0.85 solo si estas muy seguro.
10. Incluye marcas y modelos populares en Mexico cuando apliquen.
11. Si la pieza es universal, indicalo en "notes".
"""


def _extract_json(text):
    """Extract JSON object from text, handling markdown fences."""
    if text is None:
        return None
    text = text.strip()
    if text.startswith('```'):
        text = re.sub(r'^```(?:json)?\s*', '', text)
        text = re.sub(r'\s*```$', '', text)
    match = re.search(r'\{.*\}', text, re.DOTALL)
    if not match:
        return None
    try:
        return json.loads(match.group(0))
    except json.JSONDecodeError:
        return None


def _extract_vehicles(parsed):
    """Try multiple possible keys for the vehicle list."""
    for key in ['vehicles', 'vehiculos_compatibles', 'vehiculos', 'compatible_vehicles', 'results']:
        if key in parsed and isinstance(parsed[key], list):
            return parsed[key]
    # Deep search for any list containing objects with marca/modelo or make/model
    for val in parsed.values():
        if isinstance(val, list) and len(val) > 0:
            first = val[0]
            if isinstance(first, dict):
                if any(k in first for k in ['make', 'model', 'marca', 'modelo']):
                    return val
    return []


def _normalize_vehicle(v):
    """Normalize vehicle dict from QWEN to standard keys.

    Supports:
      - year: int or str (single year)
      - year_range: str like "2003-2008" or "2003-2008"
      - legacy: year as range string
    """
    make = v.get('make') or v.get('marca') or ''
    model = v.get('model') or v.get('modelo') or ''
    engine = v.get('engine') or v.get('motor') or ''
    engine_code = v.get('engine_code') or v.get('codigo_motor') or v.get('motor_code') or ''

    years = []

    # Prefer explicit year_range
    year_range = v.get('year_range') or v.get('rango_ano') or ''
    if isinstance(year_range, str):
        m = re.match(r'(\d{4})\s*[-–]\s*(\d{4})', year_range)
        if m:
            start, end = int(m.group(1)), int(m.group(2))
            years = list(range(start, end + 1))

    # Fallback to year (int or str)
    if not years:
        year_raw = v.get('year') or v.get('ano') or v.get('año') or v.get('years') or v.get('anos') or ''
        if isinstance(year_raw, int):
            years = [year_raw]
        elif isinstance(year_raw, str):
            m = re.match(r'(\d{4})\s*[-–]\s*(\d{4})', year_raw)
            if m:
                start, end = int(m.group(1)), int(m.group(2))
                years = list(range(start, end + 1))
            else:
                m2 = re.match(r'(\d{4})', year_raw)
                if m2:
                    years = [int(m2.group(1))]

    return make, model, years, engine, engine_code


def _extract_displacement(engine):
    """Extract numeric displacement (L) from engine string, e.g. '1.8L 16V' -> 1.8."""
    if not engine or engine.lower() == 'desconocido':
        return None
    # Match patterns like 1.8L, 2.0L, 3.5L, 1.6, etc.
    match = re.search(r'(\d+\.?\d*)\s*[Ll]', engine)
    if match:
        try:
            return float(match.group(1))
        except ValueError:
            return None
    return None


def _validate_vehicles(vehicles):
    """Look up each vehicle in master DB and enrich with mye_id.

    Validation strategy (in order of preference):
    1. Exact engine_code match (most precise)
    2. Displacement-based match (e.g. all 1.8L engines for that make/model/year)
    3. Broad make/model/year match (all engines for that make/model/year)

    If the master DB does not contain the vehicle (e.g. North-American models
    missing from TecDoc), the vehicle is returned with mye_id=None so it can
    be stored as a text-only QWEN record.
    """
    from tenant_db import get_master_conn
    try:
        master = get_master_conn()
        cur = master.cursor()
    except Exception:
        # Master DB unreachable — return all vehicles as unmatched text
        return [
            {'make': v.get('make') or v.get('marca') or '',
             'model': v.get('model') or v.get('modelo') or '',
             'year': v.get('year') or v.get('ano') or v.get('año') or 0,
             'engine': v.get('engine') or v.get('motor') or '',
             'engine_code': v.get('engine_code') or v.get('codigo_motor') or '',
             'mye_id': None}
            for v in vehicles
        ]

    validated = []
    seen_mye = set()
    seen_text = set()  # (make, model, year) for text-only dedup

    for v in vehicles:
        make, model, years, engine, engine_code = _normalize_vehicle(v)
        if not make or not model or not years:
            continue

        for year in years:
            matched_myes = []

            # Strategy 1: engine_code match (most precise)
            if engine_code:
                cur.execute("""
                    SELECT mye.id_mye
                    FROM model_year_engine mye
                    JOIN models m ON mye.model_id = m.id_model
                    JOIN brands b ON m.brand_id = b.id_brand
                    JOIN years y ON mye.year_id = y.id_year
                    JOIN engines e ON mye.engine_id = e.id_engine
                    WHERE b.name_brand ILIKE %s
                      AND m.name_model ILIKE %s
                      AND y.year_car = %s
                      AND e.engine_code ILIKE %s
                """, (make, f'%{model}%', year, f'%{engine_code}%'))
                matched_myes = [r[0] for r in cur.fetchall()]

            # Strategy 2: displacement-based match
            if not matched_myes:
                disp = _extract_displacement(engine)
                if disp is not None:
                    disp_pattern = f'{disp:.1f}L'
                    cur.execute("""
                        SELECT mye.id_mye
                        FROM model_year_engine mye
                        JOIN models m ON mye.model_id = m.id_model
                        JOIN brands b ON m.brand_id = b.id_brand
                        JOIN years y ON mye.year_id = y.id_year
                        JOIN engines e ON mye.engine_id = e.id_engine
                        WHERE b.name_brand ILIKE %s
                          AND m.name_model ILIKE %s
                          AND y.year_car = %s
                          AND e.name_engine ILIKE %s
                    """, (make, f'%{model}%', year, f'%{disp_pattern}%'))
                    matched_myes = [r[0] for r in cur.fetchall()]

            # Strategy 3: exact engine string match (legacy)
            if not matched_myes and engine and engine.lower() != 'desconocido':
                cur.execute("""
                    SELECT mye.id_mye
                    FROM model_year_engine mye
                    JOIN models m ON mye.model_id = m.id_model
                    JOIN brands b ON m.brand_id = b.id_brand
                    JOIN years y ON mye.year_id = y.id_year
                    JOIN engines e ON mye.engine_id = e.id_engine
                    WHERE b.name_brand ILIKE %s
                      AND m.name_model ILIKE %s
                      AND y.year_car = %s
                      AND e.name_engine ILIKE %s
                """, (make, f'%{model}%', year, engine))
                matched_myes = [r[0] for r in cur.fetchall()]

            # Strategy 4: broad make/model/year fallback (all engines)
            if not matched_myes:
                cur.execute("""
                    SELECT mye.id_mye
                    FROM model_year_engine mye
                    JOIN models m ON mye.model_id = m.id_model
                    JOIN brands b ON m.brand_id = b.id_brand
                    JOIN years y ON mye.year_id = y.id_year
                    WHERE b.name_brand ILIKE %s
                      AND m.name_model ILIKE %s
                      AND y.year_car = %s
                """, (make, f'%{model}%', year))
                matched_myes = [r[0] for r in cur.fetchall()]

            # Deduplicate and add to results
            if matched_myes:
                for mye_id in matched_myes:
                    if mye_id not in seen_mye:
                        seen_mye.add(mye_id)
                        validated.append({
                            'make': make,
                            'model': model,
                            'year': year,
                            'engine': engine,
                            'engine_code': engine_code,
                            'mye_id': mye_id,
                        })
            else:
                # No match in master DB — store as text-only QWEN record
                text_key = (make.upper(), model.upper(), year)
                if text_key not in seen_text:
                    seen_text.add(text_key)
                    validated.append({
                        'make': make,
                        'model': model,
                        'year': year,
                        'engine': engine,
                        'engine_code': engine_code,
                        'mye_id': None,
                    })

    cur.close()
    master.close()
    return validated
