package com.petshop.backend.service;

import com.petshop.backend.dto.adoption.AdoptionRequest;
import com.petshop.backend.dto.adoption.AdoptionResponse;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.entity.Adoption;
import com.petshop.backend.entity.Pet;
import com.petshop.backend.entity.Sale;
import com.petshop.backend.entity.StoreLocation;
import com.petshop.backend.entity.User;
import com.petshop.backend.exception.ResourceNotFoundException;
import com.petshop.backend.event.AdoptionConfirmedEvent;
import com.petshop.backend.event.SaleReceiptEvent;
import com.petshop.backend.repository.AdoptionRepository;
import com.petshop.backend.repository.PetRepository;
import com.petshop.backend.repository.SaleRepository;
import com.petshop.backend.repository.StoreRepository;
import com.petshop.backend.repository.UserRepository;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

@Service
public class AdoptionService {

    private static final String ADOPTION_STATUS_PENDING = "Pending";
    private static final String ADOPTION_STATUS_COMPLETED = "Completed";
    private static final String ADOPTION_STATUS_CANCELLED = "Cancelled";
    private static final String ADOPTION_STATUS_MISSED = "Missed";
    private static final String PET_STATUS_AVAILABLE = "Available";
    private static final String PET_STATUS_ADOPTED = "Adopted";
    private static final String PET_STATUS_PENDING = "Pending";

    private final AdoptionRepository adoptionRepository;
    private final PetRepository petRepository;
    private final UserRepository userRepository;
    private final StoreRepository storeRepository;
    private final SaleRepository saleRepository;
    private final ApplicationEventPublisher eventPublisher;

    public AdoptionService(AdoptionRepository adoptionRepository, PetRepository petRepository, UserRepository userRepository, StoreRepository storeRepository, SaleRepository saleRepository, ApplicationEventPublisher eventPublisher) {
        this.adoptionRepository = adoptionRepository;
        this.petRepository = petRepository;
        this.userRepository = userRepository;
        this.storeRepository = storeRepository;
        this.saleRepository = saleRepository;
        this.eventPublisher = eventPublisher;
    }

    public Page<AdoptionResponse> getAllAdoptions(String query, Long customerId, String status, Long storeId, LocalDate date, Pageable pageable) {
        String normalizedQuery = normalizeFilter(query);
        String normalizedStatus = normalizeFilter(status);

        Page<Adoption> adoptions = adoptionRepository.searchAdoptions(
                normalizedQuery,
                customerId,
                normalizedStatus,
                storeId,
                date,
                pageable
        );

        return adoptions.map(this::mapToResponse);
    }

    public AdoptionResponse getAdoptionById(Long id, Long customerId) {
        Adoption adoption = adoptionRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Adoption not found with id: " + id));

        if (customerId != null && !adoption.getCustomer().getId().equals(customerId)) {
            throw new ResourceNotFoundException("You can only view your own adoptions");
        }

        return mapToResponse(adoption);
    }

    @Transactional
    public AdoptionResponse createAdoption(AdoptionRequest request) {
        Pet pet = petRepository.findByIdForUpdate(request.getPetId())
                .orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + request.getPetId()));

        User customer = userRepository.findById(request.getCustomerId())
                .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
        User employee = resolveAdoptionEmployee(request.getEmployeeId());
        StoreLocation sourceStore = request.getSourceStoreId() != null
                ? storeRepository.findById(request.getSourceStoreId())
                        .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getSourceStoreId()))
                : null;
        String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus());
        validatePetAvailability(pet, null, null);
        if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) && request.getAdoptionDate() != null && request.getAdoptionDate().isAfter(LocalDate.now())) {
            throw new IllegalArgumentException("Cannot mark a future-dated adoption as Completed");
        }

        Adoption adoption = new Adoption();
        adoption.setPet(pet);
        adoption.setCustomer(customer);
        adoption.setEmployee(employee);
        adoption.setSourceStore(sourceStore);
        adoption.setAdoptionDate(request.getAdoptionDate());
        adoption.setAdoptionStatus(adoptionStatus);

        adoption = adoptionRepository.save(adoption);
        syncPetStatus(pet, adoptionStatus, adoption.getAdoptionId(), customer);
        if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus)) {
            createSaleForAdoption(adoption, request.getPaymentMethod());
        }
        if (ADOPTION_STATUS_PENDING.equalsIgnoreCase(adoptionStatus) || ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus)) {
            eventPublisher.publishEvent(new AdoptionConfirmedEvent(adoption.getAdoptionId()));
        }
        return mapToResponse(adoption);
    }

    @Transactional
    public AdoptionResponse updateAdoption(Long id, AdoptionRequest request) {
        Adoption adoption = adoptionRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Adoption not found with id: " + id));

        Pet pet = petRepository.findByIdForUpdate(request.getPetId())
                .orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + request.getPetId()));

        User customer = userRepository.findById(request.getCustomerId())
                .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
        User employee = resolveAdoptionEmployee(request.getEmployeeId());
        StoreLocation sourceStore = request.getSourceStoreId() != null
                ? storeRepository.findById(request.getSourceStoreId())
                        .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getSourceStoreId()))
                : null;
        boolean wasCompleted = ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoption.getAdoptionStatus());
        String previousStatus = adoption.getAdoptionStatus();
        String adoptionStatus = normalizeAdoptionStatus(request.getAdoptionStatus());
        Long currentPetId = adoption.getPet() != null ? adoption.getPet().getPetId() : null;
        validatePetAvailability(pet, adoption.getAdoptionId(), currentPetId);
        if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) && request.getAdoptionDate() != null && request.getAdoptionDate().isAfter(LocalDate.now())) {
            throw new IllegalArgumentException("Cannot mark a future-dated adoption as Completed");
        }

        adoption.setPet(pet);
        adoption.setCustomer(customer);
        adoption.setEmployee(employee);
        adoption.setSourceStore(sourceStore);
        adoption.setAdoptionDate(request.getAdoptionDate());
        adoption.setAdoptionStatus(adoptionStatus);

        adoption = adoptionRepository.save(adoption);
        syncPetStatus(pet, adoptionStatus, adoption.getAdoptionId(), customer);
        if (!wasCompleted && ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus)) {
            createSaleForAdoption(adoption, request.getPaymentMethod());
        }
        boolean statusChanged = !adoptionStatus.equalsIgnoreCase(previousStatus);
        if (statusChanged && (ADOPTION_STATUS_PENDING.equalsIgnoreCase(adoptionStatus) || ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus))) {
            eventPublisher.publishEvent(new AdoptionConfirmedEvent(adoption.getAdoptionId()));
        }
        return mapToResponse(adoption);
    }

    @Transactional
    public AdoptionResponse requestAdoption(Long customerId, Long petId, Long employeeId, Long sourceStoreId, LocalDate adoptionDate) {
        Pet pet = petRepository.findByIdForUpdate(petId)
                .orElseThrow(() -> new ResourceNotFoundException("Pet not found"));

        // Verify the pet is actually located at the claimed store
        if (pet.getStore() == null || !pet.getStore().getStoreId().equals(sourceStoreId)) {
            throw new IllegalArgumentException("The specified pet is not located at the selected store.");
        }

        // Verify the pet is available for adoption
        validatePetAvailability(pet, null, null);

        User customer = userRepository.findById(customerId)
                .orElseThrow(() -> new ResourceNotFoundException("Customer not found"));
        User employee = resolveAdoptionEmployee(employeeId);
        StoreLocation sourceStore = storeRepository.findById(sourceStoreId)
                .orElseThrow(() -> new ResourceNotFoundException("Store not found"));

        Adoption adoption = new Adoption();
        adoption.setPet(pet);
        adoption.setCustomer(customer);
        adoption.setEmployee(employee);
        adoption.setSourceStore(sourceStore);
        adoption.setAdoptionDate(adoptionDate);
        adoption.setAdoptionStatus(ADOPTION_STATUS_PENDING);

        adoption = adoptionRepository.save(adoption);
        syncPetStatus(pet, ADOPTION_STATUS_PENDING, adoption.getAdoptionId(), customer);
        return mapToResponse(adoption);
    }

    @Transactional
    public AdoptionResponse cancelAdoption(Long adoptionId, Long requestingCustomerId) {
        Adoption adoption = adoptionRepository.findById(adoptionId)
                .orElseThrow(() -> new ResourceNotFoundException("Adoption not found with id: " + adoptionId));

        if (requestingCustomerId != null && !adoption.getCustomer().getId().equals(requestingCustomerId)) {
            throw new ResourceNotFoundException("Adoption not found");
        }

        if (!ADOPTION_STATUS_PENDING.equalsIgnoreCase(adoption.getAdoptionStatus())) {
            throw new IllegalArgumentException("Only pending adoptions can be cancelled");
        }

        adoption.setAdoptionStatus(ADOPTION_STATUS_CANCELLED);
        adoption = adoptionRepository.save(adoption);
        syncPetStatus(adoption.getPet(), ADOPTION_STATUS_CANCELLED, adoption.getAdoptionId(), adoption.getCustomer());
        return mapToResponse(adoption);
    }

    @Transactional
    public void deleteAdoption(Long id) {
        Adoption adoption = adoptionRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Adoption not found with id: " + id));
        Pet pet = adoption.getPet();
        String status = adoption.getAdoptionStatus();
        adoptionRepository.delete(adoption);
        resetPetIfPending(pet, status);
    }

    @Transactional
    public void bulkDeleteAdoptions(BulkDeleteRequest request) {
        List<Adoption> adoptions = adoptionRepository.findAllById(request.getIds());
        adoptionRepository.deleteAll(adoptions);
        for (Adoption adoption : adoptions) {
            resetPetIfPending(adoption.getPet(), adoption.getAdoptionStatus());
        }
    }

    private void resetPetIfPending(Pet pet, String deletedAdoptionStatus) {
        if (!ADOPTION_STATUS_PENDING.equalsIgnoreCase(deletedAdoptionStatus)) {
            return;
        }
        if (!PET_STATUS_PENDING.equalsIgnoreCase(pet.getPetStatus())) {
            return;
        }
        boolean completedElsewhere = adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCase(
                pet.getPetId(), ADOPTION_STATUS_COMPLETED);
        if (!completedElsewhere) {
            pet.setPetStatus(PET_STATUS_AVAILABLE);
            pet.setOwner(null);
            petRepository.save(pet);
        }
    }

    private String normalizeFilter(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }

    private AdoptionResponse mapToResponse(Adoption adoption) {
        StoreLocation sourceStore = adoption.getSourceStore();
        return new AdoptionResponse(
            adoption.getAdoptionId(),
            adoption.getPet().getPetId(),
            adoption.getPet().getPetName(),
            adoption.getCustomer().getId(),
            adoption.getCustomer().getFirstName() + " " + adoption.getCustomer().getLastName(),
            adoption.getEmployee().getId(),
            adoption.getEmployee().getFirstName() + " " + adoption.getEmployee().getLastName(),
            sourceStore != null ? sourceStore.getStoreId() : null,
            sourceStore != null ? sourceStore.getStoreName() : null,
            adoption.getAdoptionDate(),
            adoption.getAdoptionStatus(),
            adoption.getPet().getPetPrice(),
            adoption.getCreatedAt(),
            adoption.getUpdatedAt()
        );
    }

    private User resolveAdoptionEmployee(Long requestedEmployeeId) {
        if (requestedEmployeeId != null) {
            User employee = userRepository.findById(requestedEmployeeId)
                    .orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + requestedEmployeeId));
            if (!isAssignableUser(employee)) {
                throw new IllegalArgumentException("Selected employee is not assignable for adoption work");
            }
            return employee;
        }

        return userRepository.findFirstByRoleAndActiveTrueOrderByIdAsc(User.Role.STAFF)
                .orElseThrow(() -> new IllegalArgumentException("No assignable staff member is available for adoption assignment"));
    }

    private boolean isAssignableUser(User user) {
        return user.getRole() == User.Role.STAFF && Boolean.TRUE.equals(user.getActive());
    }

    private String normalizeAdoptionStatus(String adoptionStatus) {
        if (adoptionStatus == null) {
            throw new IllegalArgumentException("Adoption status is required");
        }
        String trimmedStatus = adoptionStatus.trim();
        if (ADOPTION_STATUS_PENDING.equalsIgnoreCase(trimmedStatus)) {
            return ADOPTION_STATUS_PENDING;
        }
        if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(trimmedStatus)) {
            return ADOPTION_STATUS_COMPLETED;
        }
        if (ADOPTION_STATUS_CANCELLED.equalsIgnoreCase(trimmedStatus)) {
            return ADOPTION_STATUS_CANCELLED;
        }
        if (ADOPTION_STATUS_MISSED.equalsIgnoreCase(trimmedStatus)) {
            return ADOPTION_STATUS_MISSED;
        }
        throw new IllegalArgumentException("Adoption status must be Pending, Completed, Cancelled, or Missed");
    }

    private void validatePetAvailability(Pet pet, Long adoptionId, Long currentPetId) {
        boolean samePetAsCurrentAdoption = currentPetId != null && currentPetId.equals(pet.getPetId());
        boolean adoptedElsewhere = adoptionId == null
                ? adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCase(pet.getPetId(), ADOPTION_STATUS_COMPLETED)
                : adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(pet.getPetId(), ADOPTION_STATUS_COMPLETED, adoptionId);
        if (adoptedElsewhere) {
            throw new IllegalArgumentException("Selected pet has already been adopted");
        }

        if (!samePetAsCurrentAdoption && !PET_STATUS_AVAILABLE.equalsIgnoreCase(pet.getPetStatus())) {
            throw new IllegalArgumentException("Selected pet is not available for adoption");
        }
    }

    private void createSaleForAdoption(Adoption adoption, String paymentMethod) {
        if (adoption.getEmployee() == null || adoption.getSourceStore() == null) {
            return;
        }
        BigDecimal amount = adoption.getPet().getPetPrice();
        if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
            amount = BigDecimal.ZERO;
        }
        Sale sale = new Sale();
        sale.setSaleDate(LocalDateTime.now());
        sale.setEmployee(adoption.getEmployee());
        sale.setStore(adoption.getSourceStore());
        sale.setCustomer(adoption.getCustomer());
        sale.setTotalAmount(amount);
        sale.setSubtotalAmount(amount);
        sale.setPaymentMethod(paymentMethod != null && !paymentMethod.isBlank() ? paymentMethod : "Cash");
        sale.setIsRefund(false);
        sale.setChannel("ADOPTION");
        Sale savedSale = saleRepository.save(sale);
        eventPublisher.publishEvent(new SaleReceiptEvent(savedSale.getSaleId()));
    }

    private void syncPetStatus(Pet pet, String adoptionStatus, Long adoptionId, User customer) {
        boolean completedElsewhere = adoptionId != null
                && adoptionRepository.existsByPet_IdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(pet.getPetId(), ADOPTION_STATUS_COMPLETED, adoptionId);

        if (ADOPTION_STATUS_COMPLETED.equalsIgnoreCase(adoptionStatus) || completedElsewhere) {
            pet.setPetStatus(PET_STATUS_ADOPTED);
            pet.setOwner(customer);
            pet.setStore(null);
        } else if (ADOPTION_STATUS_PENDING.equalsIgnoreCase(adoptionStatus)) {
            pet.setPetStatus(PET_STATUS_PENDING);
            pet.setOwner(customer);
        } else {
            pet.setPetStatus(PET_STATUS_AVAILABLE);
            pet.setOwner(null);
        }
        petRepository.save(pet);
    }
}
