package online.wesal.wesal.service;

import online.wesal.wesal.dto.CategorizedInvitationsResponse;
import online.wesal.wesal.dto.CreateInvitationRequest;
import online.wesal.wesal.dto.InvitationResponse;
import online.wesal.wesal.entity.Attendee;
import online.wesal.wesal.entity.Invitation;
import online.wesal.wesal.entity.Tag;
import online.wesal.wesal.entity.User;
import online.wesal.wesal.repository.AttendeeRepository;
import online.wesal.wesal.repository.InvitationRepository;
import online.wesal.wesal.repository.TagRepository;
import online.wesal.wesal.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
public class InvitationService {
    
    @Autowired
    private InvitationRepository invitationRepository;
    
    @Autowired
    private TagRepository tagRepository;
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private AttendeeRepository attendeeRepository;
    
    @Autowired
    private FCMNotificationService fcmNotificationService;
    
    @Transactional
    public InvitationResponse createInvitation(CreateInvitationRequest request, String userEmail) {
        User creator = userRepository.findByEmail(userEmail)
            .orElseThrow(() -> new RuntimeException("User not found"));
        
        Tag tag = tagRepository.findById(request.getTagId())
            .orElseThrow(() -> new RuntimeException("Tag not found"));
        
        Invitation invitation = new Invitation(
            request.getTitle(),
            request.getDescription(),
            request.getMaxParticipants(),
            tag,
            creator
        );
        
        invitation.setDateTime(request.getDateTime());
        invitation.setLocation(request.getLocation());
        
        Invitation saved = invitationRepository.save(invitation);
        return mapToResponse(saved);
    }
    
    public Optional<InvitationResponse> getInvitationById(Long id) {
        return invitationRepository.findById(id)
            .map(this::mapToResponseWithAttendees);
    }
    
    public List<InvitationResponse> getAvailableInvitations() {
        return invitationRepository.findAvailableInvitationsOrderByCreationDate()
            .stream()
            .map(this::mapToResponse)
            .collect(Collectors.toList());
    }
    
    public CategorizedInvitationsResponse getAllCategorizedInvitations(String userEmail) {
        User user = userRepository.findByEmail(userEmail)
            .orElseThrow(() -> new RuntimeException("Authentication error. Please log in again"));
        
        List<InvitationResponse> created = invitationRepository.findByCreatorIdOrderByCreationDateDesc(user.getId())
            .stream()
            .map(this::mapToResponse)
            .collect(Collectors.toList());
        
        List<InvitationResponse> accepted = invitationRepository.findAcceptedInvitationsByUserIdOrderByCreationDateDesc(user.getId())
            .stream()
            .map(this::mapToResponse)
            .collect(Collectors.toList());
        
        List<InvitationResponse> available = invitationRepository.findAvailableInvitationsOrderByCreationDate()
            .stream()
            .filter(invitation -> !invitation.getCreator().getId().equals(user.getId()))
            .filter(invitation -> !attendeeRepository.existsByInvitationIdAndUserId(invitation.getId(), user.getId()))
            .map(this::mapToResponse)
            .collect(Collectors.toList());
        
        return new CategorizedInvitationsResponse(created, accepted, available);
    }
    
    @Transactional
    public void acceptInvitation(Long invitationId, String userEmail) {
        User user = userRepository.findByEmail(userEmail)
            .orElseThrow(() -> new RuntimeException("Authentication error. Please log in again"));
        
        Invitation invitation = invitationRepository.findById(invitationId)
            .orElseThrow(() -> new RuntimeException("This invitation does not exist"));
        
        if (invitation.getCreator().getId().equals(user.getId())) {
            throw new RuntimeException("You cannot accept your own invitation");
        }
        
        if (invitation.getCurrentAttendees() >= invitation.getMaxParticipants()) {
            throw new RuntimeException("This invitation is already full");
        }
        
        if (attendeeRepository.existsByInvitationIdAndUserId(invitationId, user.getId())) {
            throw new RuntimeException("You have already accepted this invitation");
        }
        
        Attendee attendee = new Attendee(invitation, user);
        attendeeRepository.save(attendee);
        
        invitation.setCurrentAttendees(invitation.getCurrentAttendees() + 1);
        int newAttendeeCount = invitation.getCurrentAttendees();
        invitationRepository.save(invitation);
        
        // If invitation is now full, notify everyone
        if (newAttendeeCount == invitation.getMaxParticipants()) {
            // Get all participants (attendees + organizer)
            List<Attendee> attendees = attendeeRepository.findByInvitationId(invitationId);
            List<User> allParticipants = attendees.stream()
                .map(Attendee::getUser)
                .collect(Collectors.toList());
            allParticipants.add(invitation.getCreator());
            
            // Extract FCM tokens and display names for async processing
            List<String> fcmTokens = allParticipants.stream()
                .map(User::getFcmToken)
                .collect(Collectors.toList());
            List<String> displayNames = allParticipants.stream()
                .map(User::getDisplayName)
                .collect(Collectors.toList());
            
            fcmNotificationService.sendInvitationFullNotification(
                fcmTokens,
                displayNames,
                invitation.getTag().getName(),
                newAttendeeCount
            );
        } else {
            // Send notification to organizer about new attendee (only if not full)
            fcmNotificationService.sendInvitationAcceptedNotification(
                invitation.getCreator().getFcmToken(),
                invitation.getCreator().getDisplayName(),
                user.getDisplayName(),
                invitation.getTag().getName()
            );
        }
    }
    
    @Transactional
    public void cancelInvitation(Long invitationId, String userEmail) {
        User user = userRepository.findByEmail(userEmail)
            .orElseThrow(() -> new RuntimeException("Authentication error. Please log in again"));
        
        Invitation invitation = invitationRepository.findById(invitationId)
            .orElse(null);
        
        if (invitation == null) {
            throw new RuntimeException("Invitation not found or already cancelled by organizer");
        }
        
        if (invitation.getDateTime() != null && invitation.getDateTime().isBefore(LocalDateTime.now())) {
            throw new RuntimeException("Cannot cancel expired invitation");
        }
        
        boolean isOrganizer = invitation.getCreator().getId().equals(user.getId());
        boolean isAttendee = attendeeRepository.existsByInvitationIdAndUserId(invitationId, user.getId());
        
        if (!isOrganizer && !isAttendee) {
            throw new RuntimeException("You are not associated with this invitation");
        }
        
        if (isOrganizer) {
            attendeeRepository.deleteByInvitationId(invitationId);
            invitationRepository.deleteById(invitationId);
        } else {
            attendeeRepository.deleteByInvitationIdAndUserId(invitationId, user.getId());
            invitation.setCurrentAttendees(invitation.getCurrentAttendees() - 1);
            invitationRepository.save(invitation);
        }
    }
    
    private InvitationResponse mapToResponse(Invitation invitation) {
        InvitationResponse response = new InvitationResponse();
        response.setId(invitation.getId());
        response.setTitle(invitation.getTitle());
        response.setDescription(invitation.getDescription());
        response.setDateTime(invitation.getDateTime());
        response.setLocation(invitation.getLocation());
        response.setMaxParticipants(invitation.getMaxParticipants());
        response.setCurrentAttendees(invitation.getCurrentAttendees());
        response.setCreatedAt(invitation.getCreatedAt());
        
        InvitationResponse.TagDto tagDto = new InvitationResponse.TagDto();
        tagDto.setId(invitation.getTag().getId());
        tagDto.setName(invitation.getTag().getName());
        tagDto.setColorHex(invitation.getTag().getColorHex());
        tagDto.setIconName(invitation.getTag().getIconName());
        response.setTag(tagDto);
        
        InvitationResponse.UserDto userDto = new InvitationResponse.UserDto();
        userDto.setId(invitation.getCreator().getId());
        userDto.setDisplayName(invitation.getCreator().getDisplayName());
        userDto.setAvatar(invitation.getCreator().getAvatar());
        response.setCreator(userDto);
        
        return response;
    }
    
    private InvitationResponse mapToResponseWithAttendees(Invitation invitation) {
        InvitationResponse response = mapToResponse(invitation);
        
        List<Attendee> attendees = attendeeRepository.findByInvitationId(invitation.getId());
        List<InvitationResponse.AttendeeDto> attendeeDtos = attendees.stream()
            .map(attendee -> {
                InvitationResponse.AttendeeDto dto = new InvitationResponse.AttendeeDto();
                dto.setId(attendee.getUser().getId());
                dto.setDisplayName(attendee.getUser().getDisplayName());
                dto.setAvatar(attendee.getUser().getAvatar());
                dto.setJoinedAt(attendee.getJoinedAt());
                return dto;
            })
            .collect(Collectors.toList());
        
        response.setAttendees(attendeeDtos);
        return response;
    }
}