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.TransfertDto;
import sn.ladoum.bergerie.entity.Mouton;
import sn.ladoum.bergerie.entity.Transfert;
import sn.ladoum.bergerie.entity.Utilisateur;
import sn.ladoum.bergerie.entity.enums.Role;
import sn.ladoum.bergerie.entity.enums.StatutTransfert;
import sn.ladoum.bergerie.exception.ResourceNotFoundException;
import sn.ladoum.bergerie.repository.MoutonRepository;
import sn.ladoum.bergerie.repository.TransfertRepository;
import sn.ladoum.bergerie.repository.UtilisateurRepository;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
@Transactional
public class TransfertService {

    private final TransfertRepository transfertRepository;
    private final MoutonRepository moutonRepository;
    private final UtilisateurRepository utilisateurRepository;

    // ----------------------------------------------------------- Lectures

    @Transactional(readOnly = true)
    public List<TransfertDto> mesTransferts() {
        Utilisateur me = currentUserOrThrow();
        // Admin voit tout, éleveur voit les transferts où il est source ou destination
        List<Transfert> list = transfertRepository.findAll().stream()
                .filter(t -> me.getRole() == Role.ADMIN
                        || (t.getSource() != null && me.getId().equals(t.getSource().getId()))
                        || (t.getDestination() != null && me.getId().equals(t.getDestination().getId())))
                .toList();
        return list.stream().map(this::toDto).toList();
    }

    @Transactional(readOnly = true)
    public List<TransfertDto> entrants() {
        Utilisateur me = currentUserOrThrow();
        return transfertRepository.findByDestinationIdAndStatut(me.getId(), StatutTransfert.EN_ATTENTE)
                .stream().map(this::toDto).toList();
    }

    @Transactional(readOnly = true)
    public List<TransfertDto> sortants() {
        Utilisateur me = currentUserOrThrow();
        return transfertRepository.findBySourceId(me.getId()).stream()
                .map(this::toDto).toList();
    }

    /** Historique public d'un mouton — accessible à tout utilisateur authentifié (fiche publique / QR). */
    @Transactional(readOnly = true)
    public List<TransfertDto> historiquePublic(Long moutonId) {
        moutonRepository.findById(moutonId)
                .orElseThrow(() -> ResourceNotFoundException.of("Mouton", moutonId));
        return transfertRepository.findByMoutonIdOrderByDateDemandeDesc(moutonId).stream()
                .map(this::toDto).toList();
    }

    @Transactional(readOnly = true)
    public List<TransfertDto> historiqueDuMouton(Long moutonId) {
        Mouton m = moutonRepository.findById(moutonId)
                .orElseThrow(() -> ResourceNotFoundException.of("Mouton", moutonId));
        // Un éleveur ne voit l'historique que si le mouton est actuellement à lui,
        // ou s'il a été source/destination d'un des transferts
        Utilisateur me = currentUserOrThrow();
        if (me.getRole() != Role.ADMIN) {
            boolean autorise = (m.getEleveur() != null && me.getId().equals(m.getEleveur().getId()))
                    || transfertRepository.findByMoutonIdOrderByDateDemandeDesc(moutonId).stream()
                    .anyMatch(t -> me.getId().equals(t.getSource().getId())
                            || me.getId().equals(t.getDestination().getId()));
            if (!autorise) {
                throw new AccessDeniedException("Vous n'avez pas accès à cet historique.");
            }
        }
        return transfertRepository.findByMoutonIdOrderByDateDemandeDesc(moutonId).stream()
                .map(this::toDto).toList();
    }

    // ----------------------------------------------------------- Actions

    public TransfertDto initier(TransfertDto dto) {
        Utilisateur me = currentUserOrThrow();

        Mouton m = moutonRepository.findById(dto.getMoutonId())
                .orElseThrow(() -> ResourceNotFoundException.of("Mouton", dto.getMoutonId()));

        if (m.getEleveur() == null) {
            throw new IllegalStateException("Ce mouton n'a pas de propriétaire.");
        }

        // Seul le propriétaire actuel (ou admin) peut initier
        if (me.getRole() != Role.ADMIN && !me.getId().equals(m.getEleveur().getId())) {
            throw new AccessDeniedException("Vous n'êtes pas propriétaire de ce mouton.");
        }

        Utilisateur destination = utilisateurRepository.findById(dto.getDestinationId())
                .orElseThrow(() -> ResourceNotFoundException.of("Utilisateur", dto.getDestinationId()));

        if (destination.getId().equals(m.getEleveur().getId())) {
            throw new IllegalArgumentException("La destination est déjà le propriétaire actuel.");
        }

        // Un seul transfert EN_ATTENTE à la fois par mouton
        Optional<Transfert> enAttente = transfertRepository
                .findFirstByMoutonIdAndStatutOrderByDateDemandeDesc(m.getId(), StatutTransfert.EN_ATTENTE);
        if (enAttente.isPresent()) {
            throw new IllegalStateException("Un transfert est déjà en attente pour ce mouton.");
        }

        Transfert t = Transfert.builder()
                .mouton(m)
                .source(m.getEleveur())
                .destination(destination)
                .dateDemande(LocalDateTime.now())
                .statut(StatutTransfert.EN_ATTENTE)
                .note(dto.getNote())
                .build();
        return toDto(transfertRepository.save(t));
    }

    public TransfertDto accepter(Long id) {
        Utilisateur me = currentUserOrThrow();
        Transfert t = getEntity(id);

        // Seule la destination peut accepter
        if (me.getRole() != Role.ADMIN && !me.getId().equals(t.getDestination().getId())) {
            throw new AccessDeniedException("Seule la bergerie de destination peut accepter ce transfert.");
        }
        if (t.getStatut() != StatutTransfert.EN_ATTENTE) {
            throw new IllegalStateException("Ce transfert n'est plus en attente.");
        }

        // Transfert de propriété
        Mouton m = t.getMouton();
        m.setEleveur(t.getDestination());
        moutonRepository.save(m);

        t.setStatut(StatutTransfert.ACCEPTE);
        t.setDateReponse(LocalDateTime.now());
        return toDto(transfertRepository.save(t));
    }

    public TransfertDto refuser(Long id, String motif) {
        Utilisateur me = currentUserOrThrow();
        Transfert t = getEntity(id);

        if (me.getRole() != Role.ADMIN && !me.getId().equals(t.getDestination().getId())) {
            throw new AccessDeniedException("Seule la bergerie de destination peut refuser ce transfert.");
        }
        if (t.getStatut() != StatutTransfert.EN_ATTENTE) {
            throw new IllegalStateException("Ce transfert n'est plus en attente.");
        }

        t.setStatut(StatutTransfert.REFUSE);
        t.setDateReponse(LocalDateTime.now());
        t.setMotifRefus(motif);
        return toDto(transfertRepository.save(t));
    }

    public TransfertDto annuler(Long id) {
        Utilisateur me = currentUserOrThrow();
        Transfert t = getEntity(id);

        if (me.getRole() != Role.ADMIN && !me.getId().equals(t.getSource().getId())) {
            throw new AccessDeniedException("Seule la bergerie d'origine peut annuler ce transfert.");
        }
        if (t.getStatut() != StatutTransfert.EN_ATTENTE) {
            throw new IllegalStateException("Ce transfert n'est plus en attente.");
        }

        t.setStatut(StatutTransfert.ANNULE);
        t.setDateReponse(LocalDateTime.now());
        return toDto(transfertRepository.save(t));
    }

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

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

    private Utilisateur currentUserOrThrow() {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth == null || !auth.isAuthenticated()) {
            throw new AccessDeniedException("Authentification requise.");
        }
        String email = auth.getName();
        return utilisateurRepository.findByEmail(email)
                .orElseThrow(() -> new AccessDeniedException("Utilisateur introuvable."));
    }

    private TransfertDto toDto(Transfert t) {
        return TransfertDto.builder()
                .id(t.getId())
                .moutonId(t.getMouton() != null ? t.getMouton().getId() : null)
                .moutonNom(t.getMouton() != null ? t.getMouton().getNom() : null)
                .moutonPhoto(t.getMouton() != null ? t.getMouton().getPhoto() : null)
                .sourceId(t.getSource() != null ? t.getSource().getId() : null)
                .sourceNom(t.getSource() != null ? t.getSource().getNom() : null)
                .sourceBergerie(t.getSource() != null ? t.getSource().getNomBergerie() : null)
                .destinationId(t.getDestination() != null ? t.getDestination().getId() : null)
                .destinationNom(t.getDestination() != null ? t.getDestination().getNom() : null)
                .destinationBergerie(t.getDestination() != null ? t.getDestination().getNomBergerie() : null)
                .dateDemande(t.getDateDemande())
                .dateReponse(t.getDateReponse())
                .statut(t.getStatut())
                .note(t.getNote())
                .motifRefus(t.getMotifRefus())
                .build();
    }
}
