package com.evseminar.service;

import com.evseminar.dto.RegistrationDTO;
import com.evseminar.dto.SeminarDTO;
import com.evseminar.dto.SeminarRegistrationRequest;
import com.evseminar.exception.BadRequestException;
import com.evseminar.exception.ResourceNotFoundException;
import com.evseminar.model.Customer;
import com.evseminar.model.Registration;
import com.evseminar.model.Seminar;
import com.evseminar.model.enums.RegistrationStatus;
import com.evseminar.repository.CustomerRepository;
import com.evseminar.repository.RegistrationRepository;
import com.evseminar.repository.SeminarRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
@RequiredArgsConstructor
public class RegistrationService {

    private final RegistrationRepository registrationRepository;
    private final SeminarRepository seminarRepository;
    private final CustomerRepository customerRepository;
    private final EmailService emailService;

    @Transactional
    public RegistrationDTO register(Long customerId, SeminarRegistrationRequest request) {
        Customer customer = customerRepository.findById(customerId)
                .orElseThrow(() -> new ResourceNotFoundException("Customer not found"));

        Seminar seminar = seminarRepository.findById(request.getSeminarId())
                .orElseThrow(() -> new ResourceNotFoundException("Seminar not found"));

        // Validate seminar is in the future
        if (seminar.getSeminarDate().isBefore(LocalDateTime.now())) {
            throw new BadRequestException("Cannot register for a past seminar");
        }

        // Check for duplicate active registration
        if (registrationRepository.existsByCustomerCustomerIdAndSeminarSeminarIdAndStatusNot(
                customerId, request.getSeminarId(), RegistrationStatus.CANCEL)) {
            throw new BadRequestException("You already have an active registration for this seminar");
        }

        // Calculate seat availability
        int bookedSeats = registrationRepository.countBookedSeats(request.getSeminarId());
        RegistrationStatus status;

        if (bookedSeats + request.getNumSeats() <= seminar.getMaxSeats()) {
            status = RegistrationStatus.SUCCESS;
        } else {
            status = RegistrationStatus.WAIT;
        }

        Registration registration = Registration.builder()
                .customer(customer)
                .seminar(seminar)
                .numSeats(request.getNumSeats())
                .status(status)
                .specialRequests(request.getSpecialRequests())
                .attended(false)
                .build();
        registrationRepository.save(registration);

        // Send email
        SeminarDTO seminarDTO = SeminarDTO.from(seminar, bookedSeats);
        if (status == RegistrationStatus.SUCCESS) {
            emailService.sendRegistrationSuccess(customer.getEmail(), customer.getName(), seminarDTO, request.getNumSeats());
        }

        return RegistrationDTO.from(registration);
    }

    @Transactional
    public RegistrationDTO cancel(Long customerId, Long registrationId) {
        Registration registration = registrationRepository.findById(registrationId)
                .orElseThrow(() -> new ResourceNotFoundException("Registration not found"));

        if (!registration.getCustomer().getCustomerId().equals(customerId)) {
            throw new BadRequestException("You can only cancel your own registration");
        }

        if (registration.getStatus() == RegistrationStatus.CANCEL) {
            throw new BadRequestException("Registration is already cancelled");
        }

        RegistrationStatus previousStatus = registration.getStatus();
        registration.setStatus(RegistrationStatus.CANCEL);
        registrationRepository.save(registration);

        // Send cancellation email
        Seminar seminar = registration.getSeminar();
        int bookedSeats = registrationRepository.countBookedSeats(seminar.getSeminarId());
        SeminarDTO seminarDTO = SeminarDTO.from(seminar, bookedSeats);
        emailService.sendRegistrationCancelled(
                registration.getCustomer().getEmail(),
                registration.getCustomer().getName(),
                seminarDTO);

        // Promote waitlisted customers if a SUCCESS registration was cancelled
        if (previousStatus == RegistrationStatus.SUCCESS) {
            promoteWaitlist(seminar);
        }

        return RegistrationDTO.from(registration);
    }

    private void promoteWaitlist(Seminar seminar) {
        int bookedSeats = registrationRepository.countBookedSeats(seminar.getSeminarId());
        List<Registration> waitlist = registrationRepository
                .findBySeminarSeminarIdAndStatusOrderByRegisteredAtAsc(
                        seminar.getSeminarId(), RegistrationStatus.WAIT);

        for (Registration waitReg : waitlist) {
            if (bookedSeats + waitReg.getNumSeats() <= seminar.getMaxSeats()) {
                waitReg.setStatus(RegistrationStatus.SUCCESS);
                registrationRepository.save(waitReg);
                bookedSeats += waitReg.getNumSeats();

                SeminarDTO seminarDTO = SeminarDTO.from(seminar, bookedSeats);
                emailService.sendWaitlistPromoted(
                        waitReg.getCustomer().getEmail(),
                        waitReg.getCustomer().getName(),
                        seminarDTO,
                        waitReg.getNumSeats());
            }
        }
    }

    public List<RegistrationDTO> getMyRegistrations(Long customerId) {
        LocalDateTime oneYearAgo = LocalDateTime.now().minusYears(1);
        return registrationRepository
                .findByCustomerCustomerIdAndRegisteredAtAfterOrderByRegisteredAtDesc(customerId, oneYearAgo)
                .stream()
                .map(RegistrationDTO::from)
                .toList();
    }

    public RegistrationDTO getRegistrationDetail(Long customerId, Long registrationId) {
        Registration registration = registrationRepository.findById(registrationId)
                .orElseThrow(() -> new ResourceNotFoundException("Registration not found"));

        if (!registration.getCustomer().getCustomerId().equals(customerId)) {
            throw new BadRequestException("You can only view your own registration");
        }

        return RegistrationDTO.from(registration);
    }
}
