from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, status
from fastapi.responses import FileResponse
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any

from pif_compiler.classes.main_workflow import receive_order, process_order_pipeline, retry_order, trigger_pipeline, Project
from pif_compiler.functions.db_utils import db_connect, get_ordine_by_id, get_all_ordini, delete_ordine
from pif_compiler.functions.common_func import generate_project_source_pdfs, create_sources_zip
from pif_compiler.functions.auth import get_current_user
from pif_compiler.functions.common_log import get_logger

logger = get_logger()
router = APIRouter()


# ==================== REQUEST / RESPONSE MODELS ====================

class OrderIngredientInput(BaseModel):
    inci: Optional[str] = None
    cas: Optional[str] = None
    percentage: float = 0.0
    is_colorante: bool = False
    skip_tox: bool = False


class OrderCreateRequest(BaseModel):
    client_name: str = Field(..., description="Nome del cliente")
    product_name: str = Field(..., description="Nome del prodotto cosmetico")
    preset_esposizione: str = Field(..., description="Nome del preset di esposizione")
    ingredients: List[OrderIngredientInput] = Field(..., description="Lista ingredienti")

    class Config:
        json_schema_extra = {
            "example": {
                "client_name": "Cosmetica Italia srl",
                "product_name": "Crema 'Cremosa'",
                "preset_esposizione": "Test Preset",
                "ingredients": [
                    {"inci": "AQUA", "cas": "", "percentage": 90, "is_colorante": False, "skip_tox": True},
                    {"inci": None, "cas": "56-81-5", "percentage": 6, "is_colorante": False, "skip_tox": False},
                    {"inci": None, "cas": "9007-16-3", "percentage": 3, "is_colorante": False, "skip_tox": False},
                    {"inci": None, "cas": "JYY-807", "percentage": 1, "is_colorante": True, "skip_tox": False}
                ]
            }
        }


class OrderCreateResponse(BaseModel):
    success: bool
    id_ordine: Optional[int] = None
    message: Optional[str] = None
    error: Optional[str] = None


# ==================== ROUTES ====================

@router.post("/orders/create", response_model=OrderCreateResponse, tags=["Orders"])
async def create_order(
    request: OrderCreateRequest,
    background_tasks: BackgroundTasks,
    user: dict = Depends(get_current_user),
):
    """
    Crea un nuovo ordine e avvia l'elaborazione in background.
    Il JSON viene salvato su MongoDB, il record su PostgreSQL (stato=RICEVUTO).
    L'arricchimento degli ingredienti avviene in background.
    Il compilatore viene valorizzato automaticamente dall'utente autenticato.
    """
    meta = user.get("user_metadata", {})
    compiler_name = meta.get("full_name") or meta.get("name") or user.get("email") or user.get("sub")
    logger.info(f"Nuovo ordine ricevuto: cliente={request.client_name}, prodotto={request.product_name}, compilatore={compiler_name}")

    try:
        raw_json = request.model_dump()
        id_ordine = receive_order(raw_json, compiler_name=compiler_name)

        if id_ordine is None:
            return OrderCreateResponse(
                success=False,
                error="Errore nel salvataggio dell'ordine"
            )

        # Avvia l'elaborazione in background
        background_tasks.add_task(process_order_pipeline)

        logger.info(f"Ordine {id_ordine} creato, elaborazione avviata in background")
        return OrderCreateResponse(
            success=True,
            id_ordine=id_ordine,
            message=f"Ordine {id_ordine} ricevuto. Elaborazione avviata in background."
        )

    except Exception as e:
        logger.error(f"Errore creazione ordine: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Errore interno: {str(e)}"
        )


@router.post("/orders/retry/{id_ordine}", response_model=OrderCreateResponse, tags=["Orders"])
async def retry_failed_order(id_ordine: int, background_tasks: BackgroundTasks):
    """
    Resetta un ordine in stato ERRORE a RICEVUTO e rilancia la pipeline.
    """
    logger.info(f"Retry ordine {id_ordine}")

    try:
        success = retry_order(id_ordine)

        if not success:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Ordine {id_ordine} non trovato o non in stato ERRORE"
            )

        background_tasks.add_task(process_order_pipeline)

        return OrderCreateResponse(
            success=True,
            id_ordine=id_ordine,
            message=f"Ordine {id_ordine} resettato. Rielaborazione avviata in background."
        )

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Errore retry ordine {id_ordine}: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Errore interno: {str(e)}"
        )


@router.post("/orders/trigger-pipeline", response_model=OrderCreateResponse, tags=["Orders"])
async def manual_trigger_pipeline(background_tasks: BackgroundTasks):
    """
    Lancia manualmente la pipeline di elaborazione.
    Processa il prossimo ordine pendente (più vecchio con stato RICEVUTO).
    """
    logger.info("Trigger manuale pipeline")

    try:
        background_tasks.add_task(trigger_pipeline)

        return OrderCreateResponse(
            success=True,
            message="Pipeline avviata in background."
        )

    except Exception as e:
        logger.error(f"Errore trigger pipeline: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Errore interno: {str(e)}"
        )


@router.get("/orders/export/{id_ordine}", tags=["Orders"])
async def export_order_excel(id_ordine: int):
    """
    Genera e scarica il file Excel per un ordine completato.
    L'ordine deve avere un uuid_progetto associato (stato >= ARRICCHITO).
    """
    logger.info(f"Export Excel per ordine {id_ordine}")

    try:
        # Recupera l'ordine dal DB
        row = get_ordine_by_id(id_ordine)
        if not row:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Ordine {id_ordine} non trovato"
            )

        uuid_progetto = row[4]  # uuid_progetto
        if not uuid_progetto:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Ordine {id_ordine} non ha un progetto associato (elaborazione non completata?)"
            )

        # Recupera il progetto da MongoDB
        from bson import ObjectId
        collection = db_connect(collection_name='projects')
        doc = collection.find_one({"_id": ObjectId(uuid_progetto)})
        if not doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Progetto {uuid_progetto} non trovato in MongoDB"
            )

        doc.pop("_id", None)
        project = Project(**doc)

        # Genera Excel
        output_path = project.export_excel()

        logger.info(f"Excel generato per ordine {id_ordine}: {output_path}")
        return FileResponse(
            path=output_path,
            filename=f"progetto_{id_ordine}.xlsx",
            media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
        )

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Errore export Excel ordine {id_ordine}: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Errore interno: {str(e)}"
        )


@router.get("/orders/export-sources/{id_ordine}", tags=["Orders"])
async def export_order_sources(id_ordine: int):
    """
    Genera i PDF delle fonti per ogni ingrediente di un ordine e li restituisce in un archivio ZIP.

    Include: PDF tossicologico del best_case (CAS_source.pdf) + PDF COSING (CAS_cosing.pdf).
    """
    logger.info(f"Export fonti PDF per ordine {id_ordine}")

    try:
        # Recupera l'ordine dal DB
        row = get_ordine_by_id(id_ordine)
        if not row:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Ordine {id_ordine} non trovato"
            )

        uuid_progetto = row[4]
        if not uuid_progetto:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Ordine {id_ordine} non ha un progetto associato"
            )

        # Recupera il progetto da MongoDB
        from bson import ObjectId
        collection = db_connect(collection_name='projects')
        doc = collection.find_one({"_id": ObjectId(uuid_progetto)})
        if not doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Progetto {uuid_progetto} non trovato in MongoDB"
            )

        doc.pop("_id", None)
        project = Project(**doc)

        # Genera i PDF delle fonti
        pdf_paths = await generate_project_source_pdfs(project)

        if not pdf_paths:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Nessuna fonte (tox/COSING) disponibile per questo progetto"
            )

        # Crea ZIP
        import os
        os.makedirs("exports", exist_ok=True)
        zip_path = f"exports/fonti_ordine_{id_ordine}.zip"
        create_sources_zip(pdf_paths, zip_path)

        logger.info(f"ZIP fonti generato per ordine {id_ordine}: {zip_path} ({len(pdf_paths)} PDF)")
        return FileResponse(
            path=zip_path,
            filename=f"fonti_ordine_{id_ordine}.zip",
            media_type="application/zip"
        )

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Errore export fonti ordine {id_ordine}: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Errore interno: {str(e)}"
        )


@router.get("/orders/list", tags=["Orders"])
async def list_orders():
    """
    Recupera la lista di tutti gli ordini con info cliente, compilatore e stato.
    Per ciascun ordine recupera product_name dal documento MongoDB.
    """
    logger.info("Richiesta lista ordini")

    try:
        rows = get_all_ordini()

        orders = []
        orders_col = db_connect(collection_name='orders')

        for row in rows:
            id_ordine, uuid_ordine, uuid_progetto, data_ordine, stato_ordine, note, \
                nome_cliente, nome_compilatore, nome_stato = row

            # Recupera product_name da MongoDB
            product_name = None
            if uuid_ordine:
                try:
                    from bson import ObjectId
                    doc = orders_col.find_one({"_id": ObjectId(uuid_ordine)}, {"product_name": 1})
                    if doc:
                        product_name = doc.get("product_name")
                except Exception:
                    pass

            orders.append({
                "id_ordine": id_ordine,
                "uuid_ordine": uuid_ordine,
                "uuid_progetto": uuid_progetto,
                "data_ordine": data_ordine.isoformat() if data_ordine else None,
                "stato_ordine": stato_ordine,
                "note": note,
                "nome_cliente": nome_cliente,
                "nome_compilatore": nome_compilatore,
                "nome_stato": nome_stato,
                "product_name": product_name,
            })

        return {"success": True, "data": orders}

    except Exception as e:
        logger.error(f"Errore lista ordini: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Errore interno: {str(e)}"
        )


@router.get("/orders/detail/{id_ordine}", tags=["Orders"])
async def get_order_detail(id_ordine: int):
    """
    Recupera il dettaglio completo di un ordine: dati PostgreSQL + documento MongoDB.
    Include product_name, preset, lista ingredienti dal JSON originale.
    """
    logger.info(f"Dettaglio ordine {id_ordine}")

    try:
        row = get_ordine_by_id(id_ordine)
        if not row:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Ordine {id_ordine} non trovato"
            )

        id_ord, id_cliente, id_compilatore, uuid_ordine, uuid_progetto, \
            data_ordine, stato_ordine, note = row

        # Recupera nomi da PostgreSQL
        from pif_compiler.functions.db_utils import postgres_connect
        conn = postgres_connect()
        with conn.cursor() as cur:
            nome_cliente = None
            if id_cliente:
                cur.execute("SELECT nome_cliente FROM clienti WHERE id_cliente = %s", (id_cliente,))
                r = cur.fetchone()
                nome_cliente = r[0] if r else None

            nome_compilatore = None
            if id_compilatore:
                cur.execute("SELECT nome_compilatore FROM compilatori WHERE id_compilatore = %s", (id_compilatore,))
                r = cur.fetchone()
                nome_compilatore = r[0] if r else None

            cur.execute("SELECT nome_stato FROM stati_ordini WHERE id_stato = %s", (stato_ordine,))
            r = cur.fetchone()
            nome_stato = r[0] if r else None
        conn.close()

        # Recupera dati dal documento MongoDB ordine
        product_name = None
        preset_esposizione = None
        ingredients = []

        if uuid_ordine:
            from bson import ObjectId
            orders_col = db_connect(collection_name='orders')
            try:
                doc = orders_col.find_one({"_id": ObjectId(uuid_ordine)})
                if doc:
                    product_name = doc.get("product_name")
                    preset_esposizione = doc.get("preset_esposizione")
                    ingredients = doc.get("ingredients", [])
            except Exception:
                pass

        return {
            "success": True,
            "order": {
                "id_ordine": id_ord,
                "uuid_ordine": uuid_ordine,
                "uuid_progetto": uuid_progetto,
                "data_ordine": data_ordine.isoformat() if data_ordine else None,
                "stato_ordine": stato_ordine,
                "nome_stato": nome_stato,
                "note": note,
                "nome_cliente": nome_cliente,
                "nome_compilatore": nome_compilatore,
                "product_name": product_name,
                "preset_esposizione": preset_esposizione,
                "ingredients": ingredients,
            }
        }

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Errore dettaglio ordine {id_ordine}: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Errore interno: {str(e)}"
        )


@router.delete("/orders/{id_ordine}", tags=["Orders"])
async def remove_order(id_ordine: int):
    """
    Elimina un ordine e tutti i dati correlati (progetto, lineage, documenti MongoDB).
    """
    logger.info(f"Eliminazione ordine {id_ordine}")

    try:
        success = delete_ordine(id_ordine)

        if not success:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Ordine {id_ordine} non trovato o errore nell'eliminazione"
            )

        return {"success": True, "message": f"Ordine {id_ordine} eliminato con successo"}

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Errore eliminazione ordine {id_ordine}: {str(e)}", exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Errore interno: {str(e)}"
        )
