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.AgneauDto;
import sn.ladoum.bergerie.dto.AgneauResumeDto;
import sn.ladoum.bergerie.dto.MoutonDto;
import sn.ladoum.bergerie.dto.NaissanceCompleteDto;
import sn.ladoum.bergerie.dto.NaissanceDto;
import sn.ladoum.bergerie.dto.NaissanceResultatDto;
import sn.ladoum.bergerie.entity.Mouton;
import sn.ladoum.bergerie.entity.Naissance;
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.StatutMouton;
import sn.ladoum.bergerie.exception.ResourceNotFoundException;
import sn.ladoum.bergerie.repository.MoutonRepository;
import sn.ladoum.bergerie.repository.NaissanceRepository;
import sn.ladoum.bergerie.repository.UtilisateurRepository;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional
public class NaissanceService {

    private final NaissanceRepository naissanceRepository;
    private final MoutonRepository moutonRepository;
    private final UtilisateurRepository utilisateurRepository;
    private final SaillieService saillieService;

    @Transactional(readOnly = true)
    public List<NaissanceDto> findAll() {
        Long eleveurId = eleveurFilterId();
        List<Naissance> resultat = (eleveurId != null)
                ? naissanceRepository.findByMereEleveurId(eleveurId)
                : naissanceRepository.findAll();
        return resultat.stream().map(this::toDto).toList();
    }

    @Transactional(readOnly = true)
    public NaissanceDto findById(Long id) {
        Naissance n = getEntity(id);
        checkAccess(n);
        return toDto(n);
    }

    public NaissanceDto create(NaissanceDto dto) {
        return toDto(naissanceRepository.save(toEntity(dto, new Naissance())));
    }

    public NaissanceResultatDto enregistrerNaissanceComplete(NaissanceCompleteDto 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 = null;
        if (dto.getPereId() != null) {
            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");
            }
        }

        Utilisateur current = currentUser();
        Utilisateur eleveur;
        if (current != null && current.getRole() == Role.ELEVEUR) {
            // Un éleveur ne peut rattacher la naissance qu'à lui-même
            eleveur = current;
        } else if (current != null && current.getRole() == Role.ADMIN) {
            // ADMIN : doit obligatoirement choisir un éleveur
            if (dto.getEleveurId() == null) {
                throw new IllegalArgumentException(
                        "L'administrateur doit choisir l'éleveur à rattacher.");
            }
            eleveur = utilisateurRepository.findById(dto.getEleveurId())
                    .orElseThrow(() -> ResourceNotFoundException.of("Utilisateur", dto.getEleveurId()));
        } else if (dto.getEleveurId() != null) {
            eleveur = utilisateurRepository.findById(dto.getEleveurId())
                    .orElseThrow(() -> ResourceNotFoundException.of("Utilisateur", dto.getEleveurId()));
        } else if (mere.getEleveur() != null) {
            eleveur = mere.getEleveur();
        } else {
            eleveur = current;
        }

        Naissance naissance = Naissance.builder()
                .date(dto.getDate())
                .pere(pere)
                .mere(mere)
                .nombrePetits(dto.getAgneaux().size())
                .build();
        naissance = naissanceRepository.save(naissance);

        // Clôture automatique de la saillie en gestation (si elle existe)
        saillieService.cloturerPourNaissance(mere.getId(), dto.getDate(), naissance);

        List<Mouton> agneauxCrees = new ArrayList<>();
        for (AgneauDto a : dto.getAgneaux()) {
            Mouton agneau = Mouton.builder()
                    .nom(a.getNom())
                    .sexe(a.getSexe())
                    .dateNaissance(a.getDateNaissance() != null ? a.getDateNaissance() : dto.getDate())
                    .dateEntreeSysteme(LocalDate.now())
                    .race("Ladoum")
                    .couleur(a.getCouleur())
                    .caracteristiques(a.getCaracteristiques())
                    .statut(StatutMouton.VIVANT)
                    .pere(pere)
                    .mere(mere)
                    .eleveur(eleveur)
                    .naissance(naissance)
                    .build();
            agneau = moutonRepository.save(agneau);
            // Génération du numéro fonctionnel JJMMYYYY#id après la première persistance
            agneau.setNumeroIdentification(
                    sn.ladoum.bergerie.service.MoutonService.genererNumero(
                            agneau.getDateNaissance(), agneau.getId()));
            agneauxCrees.add(moutonRepository.save(agneau));
        }

        return NaissanceResultatDto.builder()
                .naissance(toDto(naissance))
                .agneaux(agneauxCrees.stream().map(this::toMoutonDto).toList())
                .build();
    }

    public NaissanceDto update(Long id, NaissanceDto dto) {
        Naissance naissance = getEntity(id);
        checkAccess(naissance);
        return toDto(naissanceRepository.save(toEntity(dto, naissance)));
    }

    public void delete(Long id) {
        Naissance naissance = getEntity(id);
        checkAccess(naissance);
        naissanceRepository.delete(naissance);
    }

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

    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);
    }

    /** Retourne l'id de l'éleveur connecté si filtrage nécessaire, sinon null (admin = pas de filtre). */
    private Long eleveurFilterId() {
        Utilisateur current = currentUser();
        if (current != null && current.getRole() == Role.ELEVEUR) {
            return current.getId();
        }
        return null;
    }

    /** Rejette l'accès si l'éleveur connecté n'est pas propriétaire de la mère. */
    private void checkAccess(Naissance naissance) {
        Utilisateur current = currentUser();
        if (current == null || current.getRole() != Role.ELEVEUR) return;
        Long proprio = (naissance.getMere() != null && naissance.getMere().getEleveur() != null)
                ? naissance.getMere().getEleveur().getId() : null;
        if (!current.getId().equals(proprio)) {
            throw new AccessDeniedException("Cette naissance n'est pas rattachée à votre compte.");
        }
    }

    private Naissance toEntity(NaissanceDto dto, Naissance naissance) {
        naissance.setDate(dto.getDate());
        naissance.setNombrePetits(dto.getNombrePetits());

        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");
        }
        naissance.setMere(mere);

        if (dto.getPereId() != null) {
            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");
            }
            naissance.setPere(pere);
        } else {
            naissance.setPere(null);
        }
        return naissance;
    }

    private NaissanceDto toDto(Naissance n) {
        List<AgneauResumeDto> agneaux = moutonRepository
                .findByNaissanceId(n.getId())
                .stream()
                .map(m -> AgneauResumeDto.builder()
                        .id(m.getId())
                        .nom(m.getNom())
                        .sexe(m.getSexe())
                        .numeroIdentification(m.getNumeroIdentification())
                        .build())
                .toList();
        return NaissanceDto.builder()
                .id(n.getId())
                .date(n.getDate())
                .pereId(n.getPere() != null ? n.getPere().getId() : null)
                .pereNom(n.getPere() != null ? n.getPere().getNom() : null)
                .mereId(n.getMere().getId())
                .mereNom(n.getMere().getNom())
                .nombrePetits(n.getNombrePetits())
                .agneaux(agneaux)
                .build();
    }

    private MoutonDto toMoutonDto(Mouton m) {
        return MoutonDto.builder()
                .id(m.getId())
                .numeroIdentification(m.getNumeroIdentification())
                .nom(m.getNom())
                .sexe(m.getSexe())
                .dateNaissance(m.getDateNaissance())
                .dateEntreeSysteme(m.getDateEntreeSysteme())
                .race(m.getRace())
                .couleur(m.getCouleur())
                .caracteristiques(m.getCaracteristiques())
                .photo(m.getPhoto())
                .statut(m.getStatut())
                .pereId(m.getPere() != null ? m.getPere().getId() : null)
                .pereNom(m.getPere() != null ? m.getPere().getNom() : null)
                .mereId(m.getMere() != null ? m.getMere().getId() : null)
                .mereNom(m.getMere() != null ? m.getMere().getNom() : null)
                .eleveurId(m.getEleveur() != null ? m.getEleveur().getId() : null)
                .eleveurNom(m.getEleveur() != null ? m.getEleveur().getNom() : null)
                .build();
    }
}
