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) {
            // Si la date d'achat est présente et non la date de retrait, la date du jour devient la date de retrait pour le calcul
            var dateRetraitReelle = dateRetrait == null ? LocalDate.now() : dateRetrait;
            if (!dateAchat.isBefore(dateRetraitReelle)) {
                throw new CalculImpactException(TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(), "La durée de vie de l'équipement n'a pas pu être déterminée");
            }

            var dureeAnneeUtilisation = ChronoUnit.DAYS.between(dateAchat, dateRetraitReelle) / 365d;

            if (dureeAnneeUtilisation + result.getDureeUsageAmont() + result.getDureeUsageAval() < 1) {
                result.setValeurRetenue(1.0);
            } else {
                result.setValeurRetenue(result.getValeurRetenue() + dureeAnneeUtilisation);
            }

            result.setDateAchat(dateAchat.format(DateTimeFormatter.ISO_DATE));
            result.setDateRetrait(dateRetraitReelle.format(DateTimeFormatter.ISO_DATE));
            return result;
        }

        // dateAchat et dateRetrait sont null
        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);
            }
            return result;
        }

        // dureeUsageInterne est null
        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.getTypeItem() != null && demandeCalcul.getTypeItem().getDureeVieDefaut() != null) {
            return DureeDeVieParDefaut.builder()
                    .valeurTypeItemDureeVieDefaut(demandeCalcul.getTypeItem().getDureeVieDefaut())
                    .sourceTypeItemDureeVieDefaut(demandeCalcul.getTypeItem().getSource())
                    .valeur(demandeCalcul.getTypeItem().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");
    }
}
