package sn.ladoum.bergerie.service;

import lombok.RequiredArgsConstructor;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sn.ladoum.bergerie.dto.EtatReproducteurDto;
import sn.ladoum.bergerie.dto.SaillieDto;
import sn.ladoum.bergerie.entity.Mouton;
import sn.ladoum.bergerie.entity.Saillie;
import sn.ladoum.bergerie.entity.Utilisateur;
import sn.ladoum.bergerie.entity.enums.Role;
import sn.ladoum.bergerie.entity.enums.Sexe;
import sn.ladoum.bergerie.entity.enums.StatutSaillie;
import sn.ladoum.bergerie.exception.ResourceNotFoundException;
import sn.ladoum.bergerie.repository.MoutonRepository;
import sn.ladoum.bergerie.repository.SaillieRepository;
import sn.ladoum.bergerie.repository.UtilisateurRepository;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
@Transactional
public class SaillieService {

    public static final int GESTATION_JOURS = 150;          // moyenne
    public static final int GESTATION_MIN = 148;
    public static final int GESTATION_MAX = 152;
    public static final int REPOS_APRES_MISE_BAS_JOURS = 45;
    /** Âge minimum (mois) avant une première saillie — uniquement pour les femelles nées dans le système. */
    public static final int AGE_MIN_SAILLIE_MOIS = 7;

    private final SaillieRepository saillieRepository;
    private final MoutonRepository moutonRepository;
    private final UtilisateurRepository utilisateurRepository;

    @Transactional(readOnly = true)
    public List<SaillieDto> findAll() {
        Long eleveurId = eleveurFilterId();
        List<Saillie> list = (eleveurId != null)
                ? saillieRepository.findByMereEleveurId(eleveurId)
                : saillieRepository.findAll();
        return list.stream().map(this::toDto).toList();
    }

    @Transactional(readOnly = true)
    public SaillieDto findById(Long id) {
        Saillie s = getEntity(id);
        checkAccess(s);
        return toDto(s);
    }

    public SaillieDto create(SaillieDto dto) {
        Mouton mere = moutonRepository.findById(dto.getMereId())
                .orElseThrow(() -> ResourceNotFoundException.of("Mouton mère", dto.getMereId()));
        if (mere.getSexe() != Sexe.FEMELLE) {
            throw new IllegalArgumentException("La mère doit être de sexe FEMELLE");
        }

        Mouton pere = moutonRepository.findById(dto.getPereId())
                .orElseThrow(() -> ResourceNotFoundException.of("Mouton père", dto.getPereId()));
        if (pere.getSexe() != Sexe.MALE) {
            throw new IllegalArgumentException("Le père doit être de sexe MALE");
        }

        // Propriétaire de la saillie = propriétaire de la mère
        Utilisateur current = currentUser();
        if (current != null && current.getRole() == Role.ELEVEUR) {
            if (mere.getEleveur() == null || !mere.getEleveur().getId().equals(current.getId())) {
                throw new AccessDeniedException("Cette femelle n'appartient pas à votre bergerie.");
            }
        }

        LocalDate dateSaillie = dto.getDate() != null ? dto.getDate() : LocalDate.now();

        // Règle 0 : âge minimum pour une femelle née dans le système
        if (mere.getNaissance() != null && mere.getDateNaissance() != null) {
            long moisAge = ChronoUnit.MONTHS.between(mere.getDateNaissance(), dateSaillie);
            if (moisAge < AGE_MIN_SAILLIE_MOIS) {
                throw new IllegalStateException(
                        "Cette femelle est trop jeune pour une saillie : " + moisAge + " mois (minimum "
                                + AGE_MIN_SAILLIE_MOIS + " mois).");
            }
        }

        // Règle 1 : pas de gestation en cours sur la femelle
        Optional<Saillie> gestationActive = saillieRepository
                .findFirstByMereIdAndStatutOrderByDateDesc(mere.getId(), StatutSaillie.EN_GESTATION);
        if (gestationActive.isPresent()) {
            LocalDate prevue = gestationActive.get().getDateMiseBasPrevue();
            throw new IllegalStateException(
                    "Cette femelle est déjà en gestation. Mise bas prévue le "
                            + prevue + ".");
        }

        // Règle 2 : 45 jours de repos après la dernière mise bas
        Optional<Saillie> derniereMB = saillieRepository
                .findFirstByMereIdAndDateMiseBasReelleIsNotNullOrderByDateMiseBasReelleDesc(mere.getId());
        if (derniereMB.isPresent()) {
            LocalDate dmb = derniereMB.get().getDateMiseBasReelle();
            long joursDepuis = ChronoUnit.DAYS.between(dmb, dateSaillie);
            if (joursDepuis < REPOS_APRES_MISE_BAS_JOURS) {
                long restant = REPOS_APRES_MISE_BAS_JOURS - joursDepuis;
                throw new IllegalStateException(
                        "Cette femelle a mis bas le " + dmb + ". Il faut attendre encore "
                                + restant + " jour(s) avant une nouvelle saillie (repos minimum de "
                                + REPOS_APRES_MISE_BAS_JOURS + " jours).");
            }
        }

        Saillie saillie = Saillie.builder()
                .date(dateSaillie)
                .pere(pere)
                .mere(mere)
                .dateMiseBasPrevue(dateSaillie.plusDays(GESTATION_JOURS))
                .statut(StatutSaillie.EN_GESTATION)
                .eleveur(mere.getEleveur())
                .note(dto.getNote())
                .build();
        return toDto(saillieRepository.save(saillie));
    }

    public void delete(Long id) {
        Saillie s = getEntity(id);
        checkAccess(s);
        saillieRepository.delete(s);
    }

    public SaillieDto marquerEchec(Long id, String raison) {
        Saillie s = getEntity(id);
        checkAccess(s);
        if (s.getStatut() != StatutSaillie.EN_GESTATION) {
            throw new IllegalStateException("Seule une saillie EN_GESTATION peut être marquée en échec.");
        }
        s.setStatut(StatutSaillie.ECHEC);
        if (raison != null && !raison.isBlank()) {
            s.setNote(raison);
        }
        return toDto(saillieRepository.save(s));
    }

    /** Confirme la gestation (détection effective de la grossesse après la saillie). */
    public SaillieDto confirmer(Long id, LocalDate dateConfirmation) {
        Saillie s = getEntity(id);
        checkAccess(s);
        if (s.getStatut() != StatutSaillie.EN_GESTATION) {
            throw new IllegalStateException("Seule une saillie EN_GESTATION peut être confirmée.");
        }
        s.setConfirmee(true);
        s.setDateConfirmation(dateConfirmation != null ? dateConfirmation : LocalDate.now());
        return toDto(saillieRepository.save(s));
    }

    /** Modification d'une saillie (date, père, note, confirmation). */
    public SaillieDto update(Long id, SaillieDto dto) {
        Saillie s = getEntity(id);
        checkAccess(s);
        if (s.getStatut() == StatutSaillie.TERMINEE) {
            throw new IllegalStateException("Une saillie terminée ne peut plus être modifiée.");
        }

        if (dto.getDate() != null && !dto.getDate().equals(s.getDate())) {
            s.setDate(dto.getDate());
            s.setDateMiseBasPrevue(dto.getDate().plusDays(GESTATION_JOURS));
        }
        if (dto.getPereId() != null && !dto.getPereId().equals(s.getPere().getId())) {
            Mouton pere = moutonRepository.findById(dto.getPereId())
                    .orElseThrow(() -> ResourceNotFoundException.of("Mouton père", dto.getPereId()));
            if (pere.getSexe() != Sexe.MALE) {
                throw new IllegalArgumentException("Le père doit être de sexe MALE");
            }
            s.setPere(pere);
        }
        if (dto.getNote() != null) s.setNote(dto.getNote());
        if (dto.getConfirmee() != null) {
            s.setConfirmee(dto.getConfirmee());
            if (dto.getConfirmee() && s.getDateConfirmation() == null) {
                s.setDateConfirmation(dto.getDateConfirmation() != null
                        ? dto.getDateConfirmation() : LocalDate.now());
            }
            if (!dto.getConfirmee()) {
                s.setDateConfirmation(null);
            }
        } else if (dto.getDateConfirmation() != null) {
            s.setDateConfirmation(dto.getDateConfirmation());
        }
        return toDto(saillieRepository.save(s));
    }

    /** Clôture la saillie en gestation d'une mère au moment d'un enregistrement de naissance. */
    public Optional<Saillie> cloturerPourNaissance(Long mereId, LocalDate dateMiseBas,
                                                    sn.ladoum.bergerie.entity.Naissance naissance) {
        Optional<Saillie> active = saillieRepository
                .findFirstByMereIdAndStatutOrderByDateDesc(mereId, StatutSaillie.EN_GESTATION);
        active.ifPresent(s -> {
            s.setStatut(StatutSaillie.TERMINEE);
            s.setDateMiseBasReelle(dateMiseBas);
            s.setNaissance(naissance);
            saillieRepository.save(s);
        });
        return active;
    }

    @Transactional(readOnly = true)
    public EtatReproducteurDto etatReproducteur(Long moutonId) {
        Mouton m = moutonRepository.findById(moutonId)
                .orElseThrow(() -> ResourceNotFoundException.of("Mouton", moutonId));
        if (m.getSexe() != Sexe.FEMELLE) {
            return EtatReproducteurDto.builder()
                    .moutonId(moutonId)
                    .enGestation(false)
                    .peutEtreSaillie(false)
                    .raisonBlocage("Ce mouton est un mâle.")
                    .build();
        }

        EtatReproducteurDto.EtatReproducteurDtoBuilder b = EtatReproducteurDto.builder()
                .moutonId(moutonId)
                .enGestation(false)
                .peutEtreSaillie(true);

        LocalDate aujourdhui = LocalDate.now();

        // Âge minimum : s'applique uniquement aux femelles nées dans le système
        // (rattachées à une Naissance). Les femelles adultes importées échappent au contrôle.
        if (m.getNaissance() != null && m.getDateNaissance() != null) {
            long moisAge = ChronoUnit.MONTHS.between(m.getDateNaissance(), aujourdhui);
            if (moisAge < AGE_MIN_SAILLIE_MOIS) {
                long moisRestants = AGE_MIN_SAILLIE_MOIS - moisAge;
                return b
                        .peutEtreSaillie(false)
                        .raisonBlocage("Trop jeune pour une saillie : " + moisAge + " mois (minimum "
                                + AGE_MIN_SAILLIE_MOIS + " mois, encore " + moisRestants + " mois).")
                        .build();
            }
        }

        Optional<Saillie> active = saillieRepository
                .findFirstByMereIdAndStatutOrderByDateDesc(moutonId, StatutSaillie.EN_GESTATION);
        if (active.isPresent()) {
            Saillie s = active.get();
            int jAvant = (int) ChronoUnit.DAYS.between(aujourdhui, s.getDateMiseBasPrevue());
            int jGest = (int) ChronoUnit.DAYS.between(s.getDate(), aujourdhui);
            b.enGestation(true)
             .gestationActive(toDto(s))
             .joursAvantMiseBas(jAvant)
             .joursGestation(jGest)
             .peutEtreSaillie(false)
             .raisonBlocage("En gestation (mise bas prévue le " + s.getDateMiseBasPrevue() + ")");
        }

        Optional<Saillie> derniereMB = saillieRepository
                .findFirstByMereIdAndDateMiseBasReelleIsNotNullOrderByDateMiseBasReelleDesc(moutonId);
        if (derniereMB.isPresent()) {
            LocalDate dmb = derniereMB.get().getDateMiseBasReelle();
            int jDepuis = (int) ChronoUnit.DAYS.between(dmb, aujourdhui);
            b.derniereMiseBas(dmb).joursDepuisDerniereMiseBas(jDepuis);
            if (active.isEmpty() && jDepuis < REPOS_APRES_MISE_BAS_JOURS) {
                int restant = REPOS_APRES_MISE_BAS_JOURS - jDepuis;
                b.peutEtreSaillie(false)
                 .raisonBlocage("Repos post mise bas : encore " + restant + " jour(s) (45j minimum).");
            }
        }

        return b.build();
    }

    // ------------------------------------------------------------ Helpers

    private Saillie getEntity(Long id) {
        return saillieRepository.findById(id)
                .orElseThrow(() -> ResourceNotFoundException.of("Saillie", id));
    }

    private void checkAccess(Saillie s) {
        Utilisateur current = currentUser();
        if (current == null || current.getRole() != Role.ELEVEUR) return;
        Long proprio = (s.getMere() != null && s.getMere().getEleveur() != null)
                ? s.getMere().getEleveur().getId() : null;
        if (!current.getId().equals(proprio)) {
            throw new AccessDeniedException("Cette saillie n'est pas rattachée à votre compte.");
        }
    }

    private Long eleveurFilterId() {
        Utilisateur current = currentUser();
        if (current != null && current.getRole() == Role.ELEVEUR) {
            return current.getId();
        }
        return null;
    }

    private Utilisateur currentUser() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null || !auth.isAuthenticated()) return null;
        String email = auth.getName();
        if (email == null || "anonymousUser".equals(email)) return null;
        return utilisateurRepository.findByEmail(email).orElse(null);
    }

    private SaillieDto toDto(Saillie s) {
        Integer jAvant = null;
        Integer jGest = null;
        if (s.getStatut() == StatutSaillie.EN_GESTATION) {
            LocalDate today = LocalDate.now();
            jAvant = (int) ChronoUnit.DAYS.between(today, s.getDateMiseBasPrevue());
            jGest = (int) ChronoUnit.DAYS.between(s.getDate(), today);
        }
        return SaillieDto.builder()
                .id(s.getId())
                .date(s.getDate())
                .pereId(s.getPere() != null ? s.getPere().getId() : null)
                .pereNom(s.getPere() != null ? s.getPere().getNom() : null)
                .pereEleveurId(s.getPere() != null && s.getPere().getEleveur() != null
                        ? s.getPere().getEleveur().getId() : null)
                .pereEleveurNom(s.getPere() != null && s.getPere().getEleveur() != null
                        ? s.getPere().getEleveur().getNom() : null)
                .mereId(s.getMere() != null ? s.getMere().getId() : null)
                .mereNom(s.getMere() != null ? s.getMere().getNom() : null)
                .dateMiseBasPrevue(s.getDateMiseBasPrevue())
                .dateMiseBasReelle(s.getDateMiseBasReelle())
                .statut(s.getStatut())
                .naissanceId(s.getNaissance() != null ? s.getNaissance().getId() : null)
                .eleveurId(s.getEleveur() != null ? s.getEleveur().getId() : null)
                .eleveurNom(s.getEleveur() != null ? s.getEleveur().getNom() : null)
                .note(s.getNote())
                .confirmee(s.getConfirmee())
                .dateConfirmation(s.getDateConfirmation())
                .joursAvantMiseBas(jAvant)
                .joursGestation(jGest)
                .build();
    }
}
