package org.mte.numecoeval.calculs.domain.port.input.service.impl;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.mte.numecoeval.calculs.domain.data.demande.DemandeCalculImpactEquipementPhysique;
import org.mte.numecoeval.calculs.domain.data.erreur.TypeErreurCalcul;
import org.mte.numecoeval.calculs.domain.data.referentiel.ReferentielHypothese;
import org.mte.numecoeval.calculs.domain.data.trace.DureeDeVie;
import org.mte.numecoeval.calculs.domain.data.trace.DureeDeVieParDefaut;
import org.mte.numecoeval.calculs.domain.exception.CalculImpactException;
import org.mte.numecoeval.calculs.domain.port.input.service.DureeDeVieEquipementPhysiqueService;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

@Slf4j
@AllArgsConstructor
public class DureeDeVieEquipementPhysiqueServiceImpl implements DureeDeVieEquipementPhysiqueService {

    @Override
    public DureeDeVie calculerDureeVie(DemandeCalculImpactEquipementPhysique demandeCalcul) throws CalculImpactException {
        String methodeDureeUsage = demandeCalcul.getOptionsCalcul() != null ? demandeCalcul.getOptionsCalcul().dureeUsage() : "FIXE";

        if ("REEL".equals(methodeDureeUsage)) {
            return calculDureeUsageFromMethodeReel(demandeCalcul);
        }
        // par défaut, methodeDureeUsage == "FIXE"
        return calculDureeUsageFromMethodeFixe(demandeCalcul);
    }

    private DureeDeVie calculDureeUsageAmontEtAval(DemandeCalculImpactEquipementPhysique demandeCalcul) throws CalculImpactException {
        var result = DureeDeVie.builder()
                .valeurRetenue(0.0)
                .build();

        Double dureeUsageAmont = demandeCalcul.getEquipementPhysique().getDureeUsageAmont();
        if (dureeUsageAmont != null) {
            if (dureeUsageAmont < 0) {
                throw new CalculImpactException(TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(), "La durée d'usage amont ne peut pas être négative");
            }
            result.setDureeUsageAmont(dureeUsageAmont);
            result.setValeurRetenue(dureeUsageAmont);
        } else {
            result.setDureeUsageAmont(0.0);
        }
        Double dureeUsageAval = demandeCalcul.getEquipementPhysique().getDureeUsageAval();
        if (dureeUsageAval != null) {
            if (dureeUsageAval < 0) {
                throw new CalculImpactException(TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(), "La durée d'usage aval ne peut pas être négative");
            }
            result.setDureeUsageAval(dureeUsageAval);
            result.setValeurRetenue(result.getValeurRetenue() + dureeUsageAval);
        } else {
            result.setDureeUsageAval(0.0);
        }
        return result;
    }

    private DureeDeVie calculDureeUsageFromMethodeReel(DemandeCalculImpactEquipementPhysique demandeCalcul) throws CalculImpactException {
        DureeDeVie result = calculDureeUsageAmontEtAval(demandeCalcul);
        result.setMethodeDureeUsage("REEL");
        var dateAchat = demandeCalcul.getEquipementPhysique().getDateAchat();
        var dateRetrait = demandeCalcul.getEquipementPhysique().getDateRetrait();
        if (dateAchat != null && dateRetrait != null) {
            if (dateAchat.isBefore(dateRetrait)) {
                Double valeur;
                if ((double) ChronoUnit.MONTHS.between(dateAchat, dateRetrait) / 12 + result.getDureeUsageAmont() + result.getDureeUsageAval() < 1) {
                    result.setValeurRetenue(1.0);
                } else {
                    valeur = ChronoUnit.DAYS.between(dateAchat, dateRetrait) / 365d;
                    result.setValeurRetenue(result.getValeurRetenue() + valeur);
                }
                result.setDateAchat(dateAchat.format(DateTimeFormatter.ISO_DATE));
                result.setDateRetrait(dateRetrait.format(DateTimeFormatter.ISO_DATE));
            } else {
                throw new CalculImpactException(TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(), "La durée de vie de l'équipement n'a pas pu être déterminée");
            }
        }
        // Taiga#864 - Si la date d'achat est présente et non la date de retrait, on prend la date du jour devient la date de retrait pour le calcul
        else if (dateAchat != null) {
            result.setValeurRetenue(result.getValeurRetenue() +
                    ChronoUnit.DAYS.between(dateAchat, LocalDate.now()) / 365d
            );
            result.setDateAchat(dateAchat.format(DateTimeFormatter.ISO_DATE));
            result.setDateRetrait(LocalDate.now().format(DateTimeFormatter.ISO_DATE));
        } else {
            var dureeDeVieParDefaut = calculerDureeVieDefaut(demandeCalcul);
            result.setDureeDeVieParDefaut(dureeDeVieParDefaut);
            if (dureeDeVieParDefaut.getValeur() + result.getDureeUsageAmont() + result.getDureeUsageAval() < 1) {
                result.setValeurRetenue(1.0);
            }
            result.setValeurRetenue(result.getValeurRetenue() + dureeDeVieParDefaut.getValeur());
        }
        return result;
    }

    private DureeDeVie calculDureeUsageFromMethodeFixe(DemandeCalculImpactEquipementPhysique demandeCalcul) throws CalculImpactException {
        DureeDeVie result = calculDureeUsageAmontEtAval(demandeCalcul);
        result.setMethodeDureeUsage("FIXE");
        Double dureeUsageInterne = demandeCalcul.getEquipementPhysique().getDureeUsageInterne();
        if (dureeUsageInterne != null) {
            if (dureeUsageInterne <= 0.0) {
                throw new CalculImpactException(TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(), "La durée d'usage interne ne peut pas être négative ou nulle");
            }
            result.setDureeUsageInterne(dureeUsageInterne);
            if (dureeUsageInterne + result.getDureeUsageAmont() + result.getDureeUsageAval() < 1) {
                result.setValeurRetenue(1.0);
            } else {
                result.setValeurRetenue(result.getValeurRetenue() + dureeUsageInterne);
            }
        } else {
            var dureeDeVieParDefaut = calculerDureeVieDefaut(demandeCalcul);
            result.setDureeDeVieParDefaut(dureeDeVieParDefaut);
            if (dureeDeVieParDefaut.getValeur() + result.getDureeUsageAmont() + result.getDureeUsageAval() < 1) {
                result.setValeurRetenue(1.0);
            } else {
                result.setValeurRetenue(result.getValeurRetenue() + dureeDeVieParDefaut.getValeur());
            }
        }
        return result;
    }

    @Override
    public DureeDeVieParDefaut calculerDureeVieDefaut(DemandeCalculImpactEquipementPhysique demandeCalcul) throws CalculImpactException {

        if (demandeCalcul.getTypeEquipement() != null && demandeCalcul.getTypeEquipement().getDureeVieDefaut() != null) {
            return DureeDeVieParDefaut.builder()
                    .valeurTypeItemDureeVieDefaut(demandeCalcul.getTypeEquipement().getDureeVieDefaut())
                    .sourceTypeItemDureeVieDefaut(demandeCalcul.getTypeEquipement().getSource())
                    .valeur(demandeCalcul.getTypeEquipement().getDureeVieDefaut())
                    .build();
        }

        var refHypotheseOpt = demandeCalcul.getHypotheseFromCode("dureeVieParDefaut");
        if (refHypotheseOpt.isPresent() && refHypotheseOpt.get().getValeur() != null) {
            ReferentielHypothese hypothese = refHypotheseOpt.get();
            return DureeDeVieParDefaut.builder()
                    .valeurReferentielHypothese(hypothese.getValeur())
                    .sourceReferentielHypothese(hypothese.getSource())
                    .valeur(hypothese.getValeur())
                    .build();
        }
        throw new CalculImpactException(TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(), "La durée de vie par défaut de l'équipement n'a pas pu être déterminée");
    }
}
