import re
from datetime import date
from typing import List, NoReturn, Optional

import oracledb
from fastapi import HTTPException
from shared.database.oracle_pool import oracle_pool
from ..schemas.comanda import (
    ComandaListItem, ComandaListResponse, ComandaRequest, ComandaResponse,
)
from .. import logger

_MAX_PER_PAGE = 100


def _handle_oracle_error(e: Exception) -> NoReturn:
    """
    Map Oracle error codes to FastAPI HTTPExceptions.  Always raises.

    Code ranges:
      20001-20999  → 422 Unprocessable (business rule errors from RAISE_APPLICATION_ERROR)
      12541/12170/12154/12560 → 503 Service Unavailable (Oracle unreachable / network)
      1017         → 500 + CRITICAL log (bad credentials — config error)
      942          → 500 + CRITICAL log (missing object/grant — deployment error)
      *            → 500 + ERROR log (unexpected)
    """
    err = e.args[0]
    code = getattr(err, "code", 0)
    raw_message = getattr(err, "message", str(e))

    if 20001 <= code <= 20999:
        # Strip "ORA-2xxxx: " prefix injected by Oracle; expose the business message only
        clean = re.sub(r"^ORA-\d+:\s*", "", raw_message).strip()
        raise HTTPException(status_code=422, detail=clean)

    if code in (12541, 12170, 12154, 12560):
        raise HTTPException(
            status_code=503,
            detail="Serviciul bazei de date e temporar indisponibil",
        )

    if code == 1017:
        logger.critical("Oracle credentials rejected (ORA-01017)", exc_info=True)
        raise HTTPException(
            status_code=500,
            detail="Eroare de configurare. Contactați administratorul.",
        )

    if code == 942:
        logger.critical("Oracle object not found or grant missing (ORA-00942)", exc_info=True)
        raise HTTPException(
            status_code=500,
            detail="Eroare internă. Contactați administratorul.",
        )

    logger.error("Unexpected Oracle error ORA-%05d", code, exc_info=True)
    raise HTTPException(status_code=500, detail="Eroare internă neașteptată")


class ComandaService:
    @staticmethod
    async def creeaza_comanda(
        data: ComandaRequest,
        username: str,
    ) -> ComandaResponse:
        logger.info(
            "service_auto.create_comanda START",
            extra={
                "user": username,
                "tip": data.tip_id,
                "client_id": data.id_masiniclient,
                "id_firma": data.id_firma,
            },
        )

        async with oracle_pool.get_connection("mariusm_test") as connection:
            try:
                with connection.cursor() as cursor:
                    out_id_ordl = cursor.var(oracledb.NUMBER)
                    out_nrord = cursor.var(oracledb.STRING)

                    cursor.callproc(
                        "MARIUSM_AUTO.SP_CREEAZA_COMANDA_PROTOTIP",
                        [
                            data.tip_id,          # p_tip IN NUMBER
                            data.id_masiniclient, # p_id_masiniclient IN NUMBER
                            data.solicitari,      # p_solicitari IN VARCHAR2
                            data.id_firma,        # p_id_firma IN NUMBER
                            data.id_sucursala,    # p_id_sucursala IN NUMBER (None for parent firm)
                            out_id_ordl,          # p_id_ordl OUT NUMBER
                            out_nrord,            # p_nrord OUT VARCHAR2
                        ],
                    )

                    connection.commit()

                    id_ordl = int(out_id_ordl.getvalue())
                    nrord = out_nrord.getvalue() or ""
            except oracledb.DatabaseError as e:
                try:
                    connection.rollback()
                except Exception:
                    pass  # connection may be dead on network errors; ignore
                _handle_oracle_error(e)

        logger.info(
            "service_auto.create_comanda OK",
            extra={"user": username, "id_ordl": id_ordl, "nrord": nrord},
        )

        return ComandaResponse(
            id_ordl=id_ordl,
            nrord=nrord,
            mesaj=f"Comanda {nrord} creata cu succes.",
        )

    @staticmethod
    async def get_comenzi(
        page: int,
        per_page: int,
        validat: Optional[int],
        data_de_la: Optional[date],
        data_pana_la: Optional[date],
    ) -> ComandaListResponse:
        per_page = min(per_page, _MAX_PER_PAGE)
        offset = (page - 1) * per_page

        where_parts = ["d.sters = 0"]
        filter_params: dict = {}

        if validat is not None:
            where_parts.append("d.validat = :validat")
            filter_params["validat"] = validat

        if data_de_la is not None:
            where_parts.append("d.datai >= :data_de_la")
            filter_params["data_de_la"] = data_de_la

        if data_pana_la is not None:
            # +1 day range avoids TRUNC (keeps index use on datai)
            where_parts.append("d.datai < :data_pana_la + 1")
            filter_params["data_pana_la"] = data_pana_la

        where_clause = " AND ".join(where_parts)

        base_from = f"""
            FROM MARIUSM_AUTO.DEV_ORDL d
            LEFT JOIN MARIUSM_AUTO.NOM_LUCRARI l ON d.id_lucrare = l.id_lucrare
            LEFT JOIN MARIUSM_AUTO.AUTO_VMASINICLIENTI mc
                   ON d.id_masiniclient = mc.id_masiniclient
            LEFT JOIN MARIUSM_AUTO.DEV_TIP_DEVIZ t ON d.id_tip = t.id_tip
            WHERE {where_clause}
        """

        count_query = f"SELECT COUNT(*) {base_from}"
        data_query = f"""
            SELECT d.id_ordl, l.nrord, d.datai, d.validat, d.inchis_fortat,
                   d.id_tip, t.denumire,
                   d.id_masiniclient, mc.nrinmat, mc.marca, mc.masina,
                   mc.anfabricatie, mc.partener
            {base_from}
            ORDER BY d.datai DESC, d.id_ordl DESC
            OFFSET :offset ROWS FETCH NEXT :per_page ROWS ONLY
        """

        try:
            async with oracle_pool.get_connection("mariusm_test") as conn:
                with conn.cursor() as cur:
                    cur.execute(count_query, filter_params)
                    total = cur.fetchone()[0]

                    cur.execute(
                        data_query,
                        {**filter_params, "offset": offset, "per_page": per_page},
                    )
                    rows = cur.fetchall()
        except oracledb.DatabaseError as e:
            _handle_oracle_error(e)

        comenzi: List[ComandaListItem] = []
        for r in rows:
            (id_ordl, nrord, datai, validat_val, inchis_fortat,
             id_tip, tip_denumire,
             id_mc, nrinmat, marca, masina, an, partener) = r

            if id_mc:
                parts = []
                if marca:
                    parts.append(marca)
                if masina:
                    parts.append(masina)
                vehicul_str = " ".join(parts) if parts else "?"
                an_str = f" ({int(an)})" if an else ""
                vehicul = f"{partener or '?'} — {vehicul_str}, {nrinmat or '?'}{an_str}"
            else:
                vehicul = ""

            comenzi.append(ComandaListItem(
                id_ordl=int(id_ordl),
                nrord=nrord or "",
                datai=datai.strftime("%Y-%m-%d") if datai else None,
                validat=int(validat_val),
                inchis_fortat=int(inchis_fortat or 0),
                id_tip=int(id_tip),
                tip_denumire=tip_denumire or "",
                vehicul=vehicul,
                id_masiniclient=int(id_mc) if id_mc else None,
            ))

        logger.debug(
            "service_auto.get_comenzi page=%d per_page=%d total=%d",
            page, per_page, total,
        )

        return ComandaListResponse(
            comenzi=comenzi,
            total=total,
            page=page,
            per_page=per_page,
        )
