"""Modelo MILP para escolher a combinacao final de escalas."""

from __future__ import annotations

import numpy as np
import pandas as pd
from scipy.optimize import Bounds, LinearConstraint, milp


def resolver_milp(colunas: pd.DataFrame, rotas: pd.DataFrame, aeronaves: pd.DataFrame) -> pd.DataFrame:
    if colunas.empty:
        return colunas

    n = len(colunas)
    integrality = np.ones(n)
    bounds = Bounds(np.zeros(n), np.ones(n))
    restricoes = []

    # R1 - Cada tripulante aparece no máximo uma vez.
    for trip in sorted(set(colunas["trip1"]).union(set(colunas["trip2"]))):
        linha = np.zeros(n)
        linha[(colunas["trip1"] == trip) | (colunas["trip2"] == trip)] = 1
        restricoes.append(LinearConstraint(linha, -np.inf, 1))

    livres = aeronaves[(aeronaves["disponivel_sede"]) & (~aeronaves["em_pane"]) & (~aeronaves["em_missao_rota"])]["aeronave"].unique()
    for av in livres:
        idx_rota = (colunas["aeronave"] == av) & (colunas["tipo_escala"] == "ROTA_ACIONADA")
        idx_local = (colunas["aeronave"] == av) & (colunas["tipo_escala"] == "MISSAO_LOCAL")
        idx_sbv = (colunas["aeronave"] == av) & (colunas["tipo_escala"] == "SBV")
        if not (idx_rota | idx_sbv).any():
            raise RuntimeError(f"Modelo inviável: não há rota nem SBV candidato para a aeronave {av}.")
        linha = np.zeros(n)
        linha[idx_rota | idx_sbv] = 1
        restricoes.append(LinearConstraint(linha, 1, 1))
        if idx_rota.any():
            linha = np.zeros(n)
            linha[idx_rota] = 1
            restricoes.append(LinearConstraint(linha, -np.inf, 1))
        if idx_local.any():
            linha = np.zeros(n)
            linha[idx_local] = 1
            restricoes.append(LinearConstraint(linha, -np.inf, 1))
        if idx_rota.any() and idx_local.any():
            linha = np.zeros(n)
            linha[idx_rota | idx_local] = 1
            restricoes.append(LinearConstraint(linha, -np.inf, 1))

    for rota_id in range(1, len(rotas) + 1):
        idx = (colunas["tipo_escala"] == "ROTA_ACIONADA") & (colunas["rota_id"] == rota_id)
        if not idx.any():
            raise RuntimeError(f"Modelo inviável: não há coluna candidata para a rota acionada {rota_id}.")
        linha = np.zeros(n)
        linha[idx] = 1
        restricoes.append(LinearConstraint(linha, 1, 1))

    resultado = milp(c=-colunas["score_milp"].to_numpy(dtype=float), integrality=integrality, bounds=bounds, constraints=restricoes)
    if not resultado.success:
        raise RuntimeError(f"MILP não encontrou solução viável: {resultado.message}")
    selecionadas = resultado.x > 0.5
    return colunas.loc[selecionadas].reset_index(drop=True)
