package org.mte.numecoeval.calculs.domain.service;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mte.numecoeval.calculs.domain.data.demande.DemandeCalculImpactEquipementPhysique;
import org.mte.numecoeval.calculs.domain.data.entree.EquipementPhysique;
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.referentiel.ReferentielTypeEquipement;
import org.mte.numecoeval.calculs.domain.data.trace.DureeDeVie;
import org.mte.numecoeval.calculs.domain.exception.CalculImpactException;
import org.mte.numecoeval.calculs.domain.port.input.service.DureeDeVieEquipementPhysiqueService;
import org.mte.numecoeval.calculs.domain.port.input.service.impl.DureeDeVieEquipementPhysiqueServiceImpl;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collections;

import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
class DureeDeVieEquipementPhysiqueServiceTest {

    private final DureeDeVieEquipementPhysiqueService dureeDeVieEquipementPhysiqueService = new DureeDeVieEquipementPhysiqueServiceImpl();

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/MM/yyyy");

    @Test
     void whenTypeEquipementDoesntHaveDureeDeVie_shouldUseHypotheseForDureeDeVie()throws Exception{

        //Given
        EquipementPhysique equipementPhysique = EquipementPhysique.builder()
                .type("serveur")
                .build();
        ReferentielHypothese hypothese = ReferentielHypothese.builder()
                .code("dureeVieParDefaut")
                .valeur(1.17d)
                .build();
        ReferentielTypeEquipement typeEquipement = ReferentielTypeEquipement.builder()
                .type("serveur")
                .dureeVieDefaut(null)
                .build();
        DemandeCalculImpactEquipementPhysique demandeCalcul = DemandeCalculImpactEquipementPhysique
                .builder()
                .equipementPhysique(equipementPhysique)
                .hypotheses(Collections.singletonList(hypothese))
                .typeEquipement(typeEquipement)
                .build();

        //When
        var actual = dureeDeVieEquipementPhysiqueService.calculerDureeVie(demandeCalcul);

        //Then
        assertEquals(1.17d,actual.getValeur());
    }


    @Test
     void shouldCalculerDureeVie1d_whenEquipementPhyisqueWithValidDates() throws Exception{
        //Given
        LocalDate dateAchat = LocalDate.parse("15/08/2020",formatter);
        LocalDate dateRetrait = LocalDate.parse("15/06/2021",formatter);
        var equipement = EquipementPhysique.builder()
                .dateAchat(dateAchat)
                .dateRetrait(dateRetrait)
                .build();
        DemandeCalculImpactEquipementPhysique demandeCalcul = DemandeCalculImpactEquipementPhysique
                .builder()
                .equipementPhysique(equipement)
                .typeEquipement(null)
                .build();
        //When
        var actual = dureeDeVieEquipementPhysiqueService.calculerDureeVie(demandeCalcul);

        //Then
        assertEquals(1d,actual.getValeur());
    }

    @Test
     void shouldCalculerDureeVie365_whenEquipementPhyisqueWithValidDates()throws Exception{
        //Given
        LocalDate dateAchat = LocalDate.parse("15/08/2020",formatter);
        LocalDate dateRetrait = LocalDate.parse("15/10/2021",formatter);
        var equipement = EquipementPhysique.builder()
                .dateAchat(dateAchat)
                .dateRetrait(dateRetrait)
                .build();
        DemandeCalculImpactEquipementPhysique demandeCalcul = DemandeCalculImpactEquipementPhysique
                .builder()
                .equipementPhysique(equipement)
                .typeEquipement(null)
                .build();
        //When
        var actual = dureeDeVieEquipementPhysiqueService.calculerDureeVie(demandeCalcul);
        var expected = 426d/365d;
        //Then
        assertEquals(expected,actual.getValeur());
    }

    @Test
     void taiga864_whenEquipementPhysiqueDateRetraitIsNullShouldUseCurrentDayAsDateRetrait()throws Exception{
        //Given
        LocalDate dateAchat = LocalDate.now().minusDays(30);
        var equipement = EquipementPhysique.builder()
                .dateAchat(dateAchat)
                .dateRetrait(null)
                .build();
        DemandeCalculImpactEquipementPhysique demandeCalcul = DemandeCalculImpactEquipementPhysique
                .builder()
                .equipementPhysique(equipement)
                .typeEquipement(null)
                .build();
        //When
        var actual = dureeDeVieEquipementPhysiqueService.calculerDureeVie(demandeCalcul);
        var expected = 30/365d;
        //Then
        assertEquals(expected,actual.getValeur());
        assertEquals(dateAchat.format(DateTimeFormatter.ISO_DATE),actual.getDateAchat());
        assertEquals(LocalDate.now().format(DateTimeFormatter.ISO_DATE),actual.getDateRetrait());
    }

    @Test
     void shouldThrowException_whenInvalideDatesOrder()throws Exception{
        //Given
        LocalDate dateRetrait= LocalDate.parse("15/08/2020",formatter);
        LocalDate dateAchat = LocalDate.parse("15/10/2021",formatter);
        var equipement = EquipementPhysique.builder()
                .dateAchat(dateAchat)
                .dateRetrait(dateRetrait)
                .build();
        DemandeCalculImpactEquipementPhysique demandeCalcul = DemandeCalculImpactEquipementPhysique
                .builder()
                .equipementPhysique(equipement)
                .typeEquipement(null)
                .build();
        //When
        CalculImpactException exception =
                    //Then
                assertThrows(CalculImpactException.class,
                    ()-> dureeDeVieEquipementPhysiqueService.calculerDureeVie(demandeCalcul));

        assertEquals(TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(), exception.getErrorType());
        String expectedMessage = "La durée de vie de l'équipement n'a pas pu être déterminée";
        String actualMessage = exception.getMessage();
        assertTrue(actualMessage.contains(expectedMessage));
    }

    @Test
     void shouldReturnDureeVieDefaut_whenMissingDate()throws Exception{
        //Given
        var equipement = EquipementPhysique.builder()
                .type("laptop")
                .build();
        ReferentielHypothese hypothese = ReferentielHypothese.builder()
                .code("dureeVieParDefaut")
                .valeur(1.17d)
                .build();
        DemandeCalculImpactEquipementPhysique demandeCalcul = DemandeCalculImpactEquipementPhysique
                .builder()
                .equipementPhysique(equipement)
                .hypotheses(Collections.singletonList(hypothese))
                .typeEquipement(null)
                .build();
        //When
        DureeDeVie actual = dureeDeVieEquipementPhysiqueService.calculerDureeVie(demandeCalcul);

        //Then
        assertEquals(1.17d,actual.getValeur());
    }

    @Test
     void whenMissingDate_ShouldUseDureeDeVieTypeEquipementInPriority()throws Exception{
        //Given
        var equipement = EquipementPhysique.builder()
                .type("laptop")
                .build();
        ReferentielHypothese hypothese = ReferentielHypothese.builder()
                .code("dureeVieParDefaut")
                .valeur(1.17d)
                .build();
        ReferentielTypeEquipement typeEquipement = ReferentielTypeEquipement.builder()
                .type("laptop")
                .dureeVieDefaut(3.5d)
                .build();
        DemandeCalculImpactEquipementPhysique demandeCalcul = DemandeCalculImpactEquipementPhysique
                .builder()
                .equipementPhysique(equipement)
                .typeEquipement(typeEquipement)
                .hypotheses(Collections.singletonList(hypothese))
                .build();
        //When
        DureeDeVie actual = dureeDeVieEquipementPhysiqueService.calculerDureeVie(demandeCalcul);

        //Then
        assertEquals(3.5d,actual.getValeur());
    }

    @Test
     void whenMissingDureeForTypeAndHypothese_ShouldThrowException()throws Exception{
        //Given
        var equipement = EquipementPhysique.builder()
                .type("laptop")
                .build();
        ReferentielHypothese hypothese = ReferentielHypothese.builder()
                .code("dureeVieParDefaut")
                .valeur(null)
                .build();
        ReferentielTypeEquipement typeEquipement = ReferentielTypeEquipement.builder()
                .type("laptop")
                .dureeVieDefaut(null)
                .build();
        DemandeCalculImpactEquipementPhysique demandeCalcul = DemandeCalculImpactEquipementPhysique
                .builder()
                .equipementPhysique(equipement)
                .typeEquipement(typeEquipement)
                .hypotheses(Collections.singletonList(hypothese))
                .build();
        //When
        CalculImpactException exception = assertThrows(CalculImpactException.class, () -> dureeDeVieEquipementPhysiqueService.calculerDureeVie(demandeCalcul));

        //Then
        assertNotNull(exception, "Une exception doit être levée");
        assertEquals(TypeErreurCalcul.ERREUR_FONCTIONNELLE.getCode(), exception.getErrorType());
        assertEquals("La durée de vie par défaut de l'équipement n'a pas pu être déterminée", exception.getMessage());
    }

}