"""Geracao de colunas candidatas para o modelo MILP."""

from __future__ import annotations

from datetime import date
from itertools import combinations

import pandas as pd

from .data_io import AERONAVES
from .rules import eh_instrutor_adaptado, piloto_disponivel, qualificacao_operacional
from .utils import normalizar_texto, parse_horas


def proxima_oi(tripulante: str, aeronave: str, subprograma: str, progresso: pd.DataFrame, catalogo: pd.DataFrame) -> dict | None:
    ois = catalogo[(catalogo["aeronave"] == aeronave) & (catalogo["subprograma"] == subprograma)].sort_values("ordem")
    if ois.empty:
        return None
    concluidas = set(
        progresso[
            (progresso["tripulante"] == tripulante)
            & (progresso["aeronave"] == aeronave)
            & (progresso["subprograma"] == subprograma)
            & (progresso["concluida"] == True)
        ]["oi"]
    )
    pendentes = ois[~ois["oi"].isin(concluidas)]
    if pendentes.empty:
        return None
    row = pendentes.iloc[0].to_dict()
    row["tripulante"] = tripulante
    return row


def candidatos_missao(data_missao: date, aeronave: str, tipo: str, cadastro: pd.DataFrame, indisponibilidades: pd.DataFrame, progresso: pd.DataFrame, catalogo: pd.DataFrame, disponiveis: pd.DataFrame) -> pd.DataFrame:
    qt_col = f"qt_{aeronave.lower()}"
    permitidos = set(disponiveis["tripulante"])
    linhas = []
    for _, trip in cadastro[cadastro[qt_col]].iterrows():
        trigrama = trip["tripulante"]
        if trigrama not in permitidos or not piloto_disponivel(trigrama, data_missao, indisponibilidades):
            continue
        subprogramas = [sp for sp in str(trip[f"subprograma_{aeronave.lower()}"]).split() if sp.startswith("SP")]
        for sp in subprogramas:
            oi = proxima_oi(trigrama, aeronave, sp, progresso, catalogo)
            if not oi:
                continue
            if tipo != "TODAS" and oi["tipo_missao"] != tipo:
                continue
            oi.update(
                patente=trip.get("patente", ""),
                qt=trip.get("qt", ""),
                qualificacao=trip.get("qualificacao", ""),
                horas_totais_2026=trip.get("horas_totais_2026", 0),
                horas_realizadas_2026=trip.get("horas_realizadas_2026", 0),
                sbv_realizados_2026=trip.get("sbv_realizados_2026", 0),
                soldo=trip.get("soldo", 0),
            )
            linhas.append(oi)
    if not linhas:
        return pd.DataFrame()
    df = pd.DataFrame(linhas)
    df["horas_faltantes_paop"] = df["horas_totais_2026"] - df["horas_realizadas_2026"]
    df["prioridade_tipo"] = df["tipo_missao"].map({"LOCAL": 1, "ROTA": 2}).fillna(3)
    return df.sort_values(["horas_faltantes_paop", "prioridade_tipo", "aeronave", "subprograma", "ordem"], ascending=[False, True, True, True, True])


def aplicar_prioridades(df: pd.DataFrame) -> pd.DataFrame:
    df = df.copy()
    df["bloqueado"] = False
    df["prioridade_paop"] = 100.0
    df.loc[(df["aeronave"] == "C98") & (df["tripulante"].isin(["DOG", "MCH"])) & (df["subprograma"] == "SPQE-3"), "prioridade_paop"] = 1
    df.loc[(df["aeronave"] == "C98") & (df["tripulante"].isin(["DOG", "MCH"])) & (df["subprograma"] != "SPQE-3"), "prioridade_paop"] = 50
    df.loc[(df["aeronave"] == "C98") & (df["tripulante"].isin(["SCA", "DIL"])) & (df["subprograma"] == "SPMO-1"), "prioridade_paop"] = 1
    df.loc[(df["aeronave"] == "C98") & (df["tripulante"].isin(["SCA", "DIL"])) & (df["subprograma"] != "SPMO-1"), "prioridade_paop"] = 60
    df.loc[(df["aeronave"] == "C98") & (df["subprograma"] == "SPFO-2") & (df["horas_realizadas_2026"] < 50), "bloqueado"] = True
    return df.sort_values(["bloqueado", "prioridade_paop", "horas_faltantes_paop"], ascending=[True, True, False])


def score_dupla(p1, p2, h1, h2, s1, s2, sbv1, sbv2, criterio: str) -> float:
    beneficio = (0 if pd.isna(p1) else 100 / p1) + (0 if pd.isna(p2) else 100 / p2)
    horas_50 = (h1 < 50) + (h2 < 50)
    horas_110 = (h1 < 110) + (h2 < 110)
    custo = (0 if pd.isna(s1) else s1) + (0 if pd.isna(s2) else s2)
    sbv = (0 if pd.isna(sbv1) else sbv1) + (0 if pd.isna(sbv2) else sbv2)
    dif_sbv = abs((0 if pd.isna(sbv1) else sbv1) - (0 if pd.isna(sbv2) else sbv2))
    if criterio == "menor_custo":
        return -custo + 0.2 * beneficio + 20 * horas_50 + 10 * horas_110
    if criterio == "meta_110":
        return 100 * horas_110 + 5 * beneficio - 0.05 * custo
    if criterio == "equalizar_quadrinhos":
        return -sbv - 0.5 * dif_sbv + 0.1 * beneficio
    return 100 * horas_50 + 10 * beneficio - 0.05 * custo


def instrutores_disponiveis(data_missao: date, aeronave: str, cadastro: pd.DataFrame, indisponibilidades: pd.DataFrame, progresso: pd.DataFrame, catalogo: pd.DataFrame, disponiveis: pd.DataFrame) -> pd.DataFrame:
    qt_col = f"qt_{aeronave.lower()}"
    permitidos = set(disponiveis["tripulante"])
    rows = []
    for _, row in cadastro[cadastro[qt_col]].iterrows():
        trip = row["tripulante"]
        if trip in permitidos and piloto_disponivel(trip, data_missao, indisponibilidades) and eh_instrutor_adaptado(trip, aeronave, cadastro, progresso, catalogo):
            rows.append(row)
    return pd.DataFrame(rows)


def gerar_duplas(candidatos: pd.DataFrame, criterio: str, tipo_escala: str, aeronave: str, data_planejamento: date, cadastro: pd.DataFrame, indisponibilidades: pd.DataFrame, progresso: pd.DataFrame, catalogo: pd.DataFrame, disponiveis: pd.DataFrame, rota_id=None, rota=None, observacao="") -> pd.DataFrame:
    if candidatos.empty:
        return pd.DataFrame()
    livres = candidatos[~candidatos["bloqueado"]].drop_duplicates("tripulante")
    linhas = []
    if tipo_escala in {"ROTA_ACIONADA", "MISSAO_LOCAL"}:
        instrutores = instrutores_disponiveis(data_planejamento, aeronave, cadastro, indisponibilidades, progresso, catalogo, disponiveis)
        for _, aluno in livres.iterrows():
            for _, inst in instrutores.iterrows():
                if aluno["tripulante"] == inst["tripulante"]:
                    continue
                linhas.append(_linha_coluna(data_planejamento, tipo_escala, aeronave, aluno, inst, criterio, rota_id, rota, observacao))
        return pd.DataFrame(linhas)
    for t1, t2 in combinations(livres["tripulante"].unique(), 2):
        info1 = livres[livres["tripulante"] == t1].iloc[0]
        info2 = livres[livres["tripulante"] == t2].iloc[0]
        linha = _linha_coluna(data_planejamento, tipo_escala, aeronave, info1, info2, criterio, rota_id, rota, observacao)
        linha["oi_trip1"] = ""
        linha["oi_trip2"] = ""
        linhas.append(linha)
    return pd.DataFrame(linhas)


def _linha_coluna(data_planejamento, tipo_escala, aeronave, info1, info2, criterio, rota_id, rota, observacao):
    h2 = info2.get("horas_realizadas_2026", 999)
    p2 = info2.get("prioridade_paop", pd.NA)
    score = score_dupla(info1.get("prioridade_paop", pd.NA), p2, info1.get("horas_realizadas_2026", 0), h2, info1.get("soldo", 0), info2.get("soldo", 0), info1.get("sbv_realizados_2026", 0), info2.get("sbv_realizados_2026", 0), criterio)
    return {
        "data": data_planejamento,
        "tipo_escala": tipo_escala,
        "rota_id": rota_id,
        "aeronave": aeronave,
        "origem": "" if rota is None else rota.get("origem", ""),
        "destino": "" if rota is None else rota.get("destino", ""),
        "inicio": pd.NaT if rota is None else rota.get("inicio", pd.NaT),
        "fim": pd.NaT if rota is None else rota.get("fim", pd.NaT),
        "tev_horas": pd.NA if rota is None else rota.get("tev_horas", pd.NA),
        "pernoite_dias": pd.NA if rota is None else rota.get("pernoite_dias", pd.NA),
        "rota": "" if rota is None else rota.get("rota", ""),
        "trip1": info1["tripulante"],
        "trip2": info2["tripulante"],
        "oi_trip1": info1.get("oi", ""),
        "oi_trip2": "",
        "prioridade_paop_1": info1.get("prioridade_paop", pd.NA),
        "prioridade_paop_2": p2,
        "custo_financeiro": info1.get("soldo", 0) + info2.get("soldo", 0),
        "score": score,
        "observacao": observacao,
    }


def gerar_colunas(data_planejamento, rotas, aeronaves, cadastro, indisponibilidades, progresso, catalogo, disponiveis, criterio_missao, criterio_sbv):
    livres = aeronaves[(aeronaves["disponivel_sede"]) & (~aeronaves["em_pane"]) & (~aeronaves["em_missao_rota"])]["aeronave"].tolist()
    colunas = []
    for rota_id, rota in rotas.iterrows():
        for av in livres:
            horas_disp = horas_disponiveis(aeronaves, av)
            horas_rota = parse_horas(rota.get("tev_horas"))
            if horas_rota is None and pd.notna(rota.get("duracao_horas")):
                horas_rota = float(rota.get("duracao_horas"))
            if horas_rota is not None and horas_rota > horas_disp:
                continue
            candidatos = candidatos_missao(data_planejamento, av, "ROTA", cadastro, indisponibilidades, progresso, catalogo, disponiveis)
            if len(candidatos) < 2:
                candidatos = candidatos_missao(data_planejamento, av, "TODAS", cadastro, indisponibilidades, progresso, catalogo, disponiveis)
            candidatos = filtrar_por_horas_aeronave(candidatos, horas_disp)
            if candidatos.empty:
                continue
            colunas.append(gerar_duplas(aplicar_prioridades(candidatos), criterio_missao, "ROTA_ACIONADA", av, data_planejamento, cadastro, indisponibilidades, progresso, catalogo, disponiveis, rota_id=rota_id + 1, rota=rota, observacao="Missão de rota acionada"))
    for av in livres:
        horas_disp = horas_disponiveis(aeronaves, av)
        candidatos_local = candidatos_missao(data_planejamento, av, "LOCAL", cadastro, indisponibilidades, progresso, catalogo, disponiveis)
        candidatos_local = filtrar_por_horas_aeronave(candidatos_local, horas_disp)
        if not candidatos_local.empty:
            colunas.append(gerar_duplas(aplicar_prioridades(candidatos_local), criterio_missao, "MISSAO_LOCAL", av, data_planejamento, cadastro, indisponibilidades, progresso, catalogo, disponiveis, observacao="Missão local planejada para progressão operacional"))
        candidatos_sbv = []
        for _, row in cadastro[cadastro[f"qt_{av.lower()}"]].iterrows():
            if row["tripulante"] in set(disponiveis["tripulante"]) and piloto_disponivel(row["tripulante"], data_planejamento, indisponibilidades):
                if qualificacao_operacional(row["tripulante"], av, cadastro, progresso, catalogo) != "AL":
                    r = row.copy()
                    r["aeronave"] = av
                    r["subprograma"] = ""
                    r["oi"] = ""
                    r["prioridade_paop"] = max(1, 999 - float(r.get("horas_totais_2026", 0) - r.get("horas_realizadas_2026", 0)))
                    r["bloqueado"] = False
                    candidatos_sbv.append(r)
        if len(candidatos_sbv) >= 2:
            colunas.append(gerar_duplas(pd.DataFrame(candidatos_sbv), criterio_sbv, "SBV", av, data_planejamento, cadastro, indisponibilidades, progresso, catalogo, disponiveis, observacao="Sobreaviso para missão de rota"))
    if not colunas:
        return pd.DataFrame()
    df = pd.concat(colunas, ignore_index=True)
    bonus = {"ROTA_ACIONADA": 100000, "MISSAO_LOCAL": 1000, "SBV": 0}
    df["coluna_id"] = range(1, len(df) + 1)
    df["score_milp"] = df["score"] + df["tipo_escala"].map(bonus).fillna(0)
    return df


def horas_disponiveis(aeronaves: pd.DataFrame, aeronave: str) -> float:
    linha = aeronaves[aeronaves["aeronave"] == aeronave]
    if linha.empty:
        return float("inf")
    horas = parse_horas(linha.iloc[0].get("hora_livre"))
    return float("inf") if horas is None else horas


def filtrar_por_horas_aeronave(candidatos: pd.DataFrame, horas_disp: float) -> pd.DataFrame:
    if candidatos.empty or horas_disp == float("inf") or "tev_horas" not in candidatos.columns:
        return candidatos
    horas_oi = candidatos["tev_horas"].map(parse_horas)
    return candidatos[(horas_oi.isna()) | (horas_oi <= horas_disp)]
