package com.dne.ems.service.impl;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import com.dne.ems.dto.FeedbackResponseDTO;
import com.dne.ems.dto.FeedbackStatsResponse;
import com.dne.ems.dto.FeedbackSubmissionRequest;
import com.dne.ems.dto.ProcessFeedbackRequest;
import com.dne.ems.dto.PublicFeedbackRequest;
import com.dne.ems.dto.TaskDetailDTO;
import com.dne.ems.event.FeedbackSubmittedForAiReviewEvent;
import com.dne.ems.exception.ResourceNotFoundException;
import com.dne.ems.model.Feedback;
import com.dne.ems.model.UserAccount;
import com.dne.ems.model.enums.FeedbackStatus;
import com.dne.ems.model.enums.PollutionType;
import com.dne.ems.model.enums.Role;
import com.dne.ems.model.enums.SeverityLevel;
import com.dne.ems.repository.FeedbackRepository;
import com.dne.ems.repository.TaskRepository;
import com.dne.ems.repository.UserAccountRepository;
import com.dne.ems.security.CustomUserDetails;
import com.dne.ems.service.FeedbackService;
import com.dne.ems.service.FileStorageService;
import com.dne.ems.service.OperationLogService;
import com.dne.ems.service.TaskManagementService;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class FeedbackServiceImpl implements FeedbackService {

    private final FeedbackRepository feedbackRepository;
    private final UserAccountRepository userAccountRepository;
    private final ApplicationEventPublisher eventPublisher;
    private final FileStorageService fileStorageService;
    private final TaskRepository taskRepository;
    private final TaskManagementService taskManagementService;
    private final OperationLogService operationLogService;

    @SuppressWarnings("deprecation")
    @Override
    @Transactional
    public Feedback submitFeedback(FeedbackSubmissionRequest request, MultipartFile[] files) {
        CustomUserDetails currentUser = (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        Long userId = currentUser.getId();

        UserAccount userAccount = userAccountRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + userId));

        Feedback feedback = new Feedback();
        feedback.setTitle(request.title());
        feedback.setDescription(request.description());
        feedback.setPollutionType(request.pollutionType());
        feedback.setSeverityLevel(request.severityLevel());
        
        feedback.setLatitude(request.location().latitude());
        feedback.setLongitude(request.location().longitude());
        feedback.setTextAddress(request.location().textAddress());
        feedback.setGridX(request.location().gridX());
        feedback.setGridY(request.location().gridY());

        feedback.setEventId(UUID.randomUUID().toString());
        feedback.setStatus(FeedbackStatus.AI_REVIEWING);
        feedback.setCreatedAt(LocalDateTime.now());
        feedback.setUpdatedAt(LocalDateTime.now());
        feedback.setSubmitterId(userId);
        feedback.setUser(userAccount);

        Feedback savedFeedback = feedbackRepository.save(feedback);

        if (files != null && files.length > 0) {
            Arrays.stream(files)
                  .filter(file -> file != null && !file.isEmpty())
                  .forEach(file -> fileStorageService.storeFileAndCreateAttachment(file, savedFeedback));
        }
        
        eventPublisher.publishEvent(new FeedbackSubmittedForAiReviewEvent(this, savedFeedback));
        
        return savedFeedback;
    }

    @Override
    public Page<FeedbackResponseDTO> getFeedback(
            CustomUserDetails userDetails,
            FeedbackStatus status, 
            PollutionType pollutionType, 
            SeverityLevel severityLevel,
            String cityName,
            String districtName,
            LocalDate startDate, 
            LocalDate endDate, 
            String keyword, 
            Pageable pageable) {
        
        List<Feedback> allFeedback = feedbackRepository.findAll();

        // Role-based filtering
        List<Feedback> roleFilteredFeedback;
        if (userDetails.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_" + Role.PUBLIC_SUPERVISOR))) {
            roleFilteredFeedback = allFeedback.stream()
                .filter(f -> f.getSubmitterId().equals(userDetails.getId()))
                .collect(Collectors.toList());
        } else if (!userDetails.getAuthorities().stream().anyMatch(a -> 
                a.getAuthority().equals("ROLE_" + Role.ADMIN) ||
                a.getAuthority().equals("ROLE_" + Role.SUPERVISOR) ||
                a.getAuthority().equals("ROLE_" + Role.DECISION_MAKER))) {
            roleFilteredFeedback = List.of();
        } else {
            roleFilteredFeedback = allFeedback;
        }

        // In-memory filtering for other criteria
        List<Feedback> finalFilteredList = roleFilteredFeedback.stream()
            .filter(f -> status == null || filterByStatus(f, status))
            .filter(f -> pollutionType == null || f.getPollutionType() == pollutionType)
            .filter(f -> severityLevel == null || f.getSeverityLevel() == severityLevel)
            .filter(f -> !StringUtils.hasText(cityName) || (f.getTextAddress() != null && f.getTextAddress().toLowerCase().contains(cityName.toLowerCase())))
            .filter(f -> !StringUtils.hasText(districtName) || (f.getTextAddress() != null && f.getTextAddress().toLowerCase().contains(districtName.toLowerCase())))
            .filter(f -> startDate == null || !f.getCreatedAt().toLocalDate().isBefore(startDate))
            .filter(f -> endDate == null || !f.getCreatedAt().toLocalDate().isAfter(endDate))
            .filter(f -> !StringUtils.hasText(keyword) || (f.getTitle() != null && f.getTitle().toLowerCase().contains(keyword.toLowerCase())))
            .collect(Collectors.toList());

        // Manual pagination
        int start = (int) pageable.getOffset();
        int end = Math.min((start + pageable.getPageSize()), finalFilteredList.size());
        
        List<Feedback> pageContent = (start > finalFilteredList.size()) ? List.of() : finalFilteredList.subList(start, end);

        Page<Feedback> feedbackPage = new PageImpl<>(pageContent, pageable, finalFilteredList.size());
        return feedbackPage.map(this::convertToDto);
    }
    
    private boolean filterByStatus(Feedback feedback, FeedbackStatus status) {
        switch (status) {
            case PROCESSED, RESOLVED, COMPLETED -> {
                if (feedback.getStatus() == FeedbackStatus.PROCESSED || feedback.getStatus() == FeedbackStatus.RESOLVED) {
                    return true;
                }
                return taskRepository.findByFeedback(feedback)
                        .map(task -> task.getStatus() == com.dne.ems.model.enums.TaskStatus.COMPLETED)
                        .orElse(false);
            }
            case IN_PROGRESS, ASSIGNED -> {
                com.dne.ems.model.enums.TaskStatus targetTaskStatus =
                        status == FeedbackStatus.IN_PROGRESS
                        ? com.dne.ems.model.enums.TaskStatus.IN_PROGRESS 
                        : com.dne.ems.model.enums.TaskStatus.ASSIGNED;
                return taskRepository.findByFeedback(feedback)
                        .map(task -> task.getStatus() == targetTaskStatus)
                        .orElse(false);
            }
            default -> {
                return feedback.getStatus() == status;
            }
        }
    }

    private FeedbackResponseDTO convertToDto(Feedback feedback) {
        TaskDetailDTO taskDetail = taskRepository.findByFeedback(feedback)
            .map(task -> taskManagementService.getTaskDetails(task.getId()))
            .orElse(null);

        return FeedbackResponseDTO.fromEntity(feedback, taskDetail);
    }

    @Override
    public Feedback getFeedbackById(Long id) {
        return feedbackRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Feedback not found with id: " + id));
    }

    @Override
    public FeedbackStatsResponse getFeedbackStats(CustomUserDetails userDetails) {
        int total ;
        int pending = 0;
        int confirmed = 0;
        int assigned = 0;
        int inProgress = 0;
        int resolved = 0;
        int closed = 0;
        int rejected = 0;
        int pendingReview = 0;
        int pendingAssignment = 0;
        int aiReviewing = 0;

        List<Feedback> feedbacks;

        if (userDetails.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_" + Role.PUBLIC_SUPERVISOR))) {
            Long userId = userDetails.getId();
            feedbacks = feedbackRepository.findBySubmitterId(userId);
        } else if (userDetails.getAuthorities().stream().anyMatch(a -> 
                a.getAuthority().equals("ROLE_" + Role.ADMIN) ||
                a.getAuthority().equals("ROLE_" + Role.SUPERVISOR) ||
                a.getAuthority().equals("ROLE_" + Role.DECISION_MAKER))) {
            feedbacks = feedbackRepository.findAll();
        } else {
            feedbacks = List.of();
        }

        for (Feedback feedback : feedbacks) {
            switch (feedback.getStatus()) {
                case AI_REVIEWING -> {
                    aiReviewing++;
                    pending++;
                }
                case PENDING_REVIEW -> {
                    pendingReview++;
                    pending++;
                }
                case PENDING_ASSIGNMENT -> {
                    pendingAssignment++;
                    pending++;
                }
                case CONFIRMED -> confirmed++;
                case ASSIGNED -> assigned++;
                case PROCESSED, AI_PROCESSING -> inProgress++;
                case CLOSED_INVALID -> closed++;
                case AI_REVIEW_FAILED -> rejected++;
                default -> {
                }
            }
        }
        total = feedbacks.size();

        return FeedbackStatsResponse.builder()
                .total(total)
                .pending(pending)
                .confirmed(confirmed)
                .assigned(assigned)
                .inProgress(inProgress)
                .resolved(resolved)
                .closed(closed)
                .rejected(rejected)
                .pendingReview(pendingReview)
                .pendingAssignment(pendingAssignment)
                .aiReviewing(aiReviewing)
                .build();
    }

    @SuppressWarnings("deprecation")
    @Override
    @Transactional
    public Feedback createPublicFeedback(PublicFeedbackRequest request, MultipartFile[] files) {
        Feedback feedback = new Feedback();
        feedback.setTitle(request.getTitle());
        feedback.setDescription(request.getDescription());
        feedback.setSeverityLevel(request.getSeverityLevel());
        if (request.getGridX() != null) {
            feedback.setGridX(request.getGridX().intValue());
        }
        if (request.getGridY() != null) {
            feedback.setGridY(request.getGridY().intValue());
        }
        try {
             feedback.setPollutionType(PollutionType.valueOf(request.getPollutionType().toUpperCase()));
        } catch (IllegalArgumentException e) {
             // For now, we can ignore or set a default
             feedback.setPollutionType(PollutionType.OTHER);
        }
        feedback.setTextAddress(request.getTextAddress());
        feedback.setEventId(UUID.randomUUID().toString());
        feedback.setStatus(FeedbackStatus.PENDING_REVIEW);
        feedback.setCreatedAt(LocalDateTime.now());
        feedback.setUpdatedAt(LocalDateTime.now());

        Feedback savedFeedback = feedbackRepository.save(feedback);

        if (files != null && files.length > 0) {
            Arrays.stream(files)
                  .filter(file -> file != null && !file.isEmpty())
                  .forEach(file -> fileStorageService.storeFileAndCreateAttachment(file, savedFeedback));
        }

        return savedFeedback;
    }

    @Override
    public FeedbackResponseDTO getFeedbackDetail(Long id) {
        Feedback feedback = getFeedbackById(id);
        return convertToDto(feedback);
    }

    @Override
    public FeedbackResponseDTO processFeedback(Long id, ProcessFeedbackRequest request) {
        Feedback feedback = feedbackRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Feedback not found with id: " + id));

        if (feedback.getStatus() != FeedbackStatus.PENDING_REVIEW) {
            throw new IllegalStateException("Only feedback with PENDING_REVIEW status can be processed. Current status: " + feedback.getStatus());
        }

        feedback.setStatus(request.getStatus());
        feedback.setUpdatedAt(LocalDateTime.now());
        
        Feedback updatedFeedback = feedbackRepository.save(feedback);

        // 记录操作日志
        com.dne.ems.model.enums.OperationType operationType;
        String description;
        String notes = request.getNotes() != null ? request.getNotes() : "";

        switch (updatedFeedback.getStatus()) {
            case PENDING_ASSIGNMENT -> {
                operationType = com.dne.ems.model.enums.OperationType.APPROVE_FEEDBACK;
                description = "反馈已批准，待分配。备注: " + notes;
            }
            case CLOSED_INVALID -> {
                operationType = com.dne.ems.model.enums.OperationType.REJECT_FEEDBACK;
                description = "反馈已拒绝。备注: " + notes;
            }
            default -> {
                operationType = com.dne.ems.model.enums.OperationType.UPDATE;
                description = "反馈状态更新为 " + updatedFeedback.getStatus() + "。备注: " + notes;
            }
        }

        operationLogService.recordOperation(
            operationType, 
            description, 
            updatedFeedback.getId().toString(), 
            "Feedback"
        );

        return FeedbackResponseDTO.fromEntity(updatedFeedback, null);
    }

    @SuppressWarnings("unused")
    private FeedbackStatsResponse calculateStats(List<Feedback> feedbackList) {
        int total = feedbackList.size();
        int pendingReview = (int) feedbackList.stream().filter(f -> f.getStatus() == FeedbackStatus.PENDING_REVIEW).count();
        int pendingAssignment = (int) feedbackList.stream().filter(f -> f.getStatus() == FeedbackStatus.PENDING_ASSIGNMENT).count();
        int aiReviewing = (int) feedbackList.stream().filter(f -> f.getStatus() == FeedbackStatus.AI_REVIEWING).count();
        int confirmed = (int) feedbackList.stream().filter(f -> f.getStatus() == FeedbackStatus.CONFIRMED).count();
        int assigned = (int) feedbackList.stream().filter(f -> f.getStatus() == FeedbackStatus.ASSIGNED).count();
        int inProgress = (int) feedbackList.stream().filter(f -> f.getStatus() == FeedbackStatus.IN_PROGRESS).count();
        int resolved = (int) feedbackList.stream().filter(f -> f.getStatus() == FeedbackStatus.RESOLVED).count();
        int closed = (int) feedbackList.stream().filter(f -> f.getStatus() == FeedbackStatus.CLOSED_INVALID).count();

        // The "pending" count is a summary of all statuses that require action
        int pending = pendingReview + pendingAssignment + aiReviewing;
        
        // The 'rejected' field in DTO doesn't map to a single enum, using 0.
        // CLOSED_INVALID is counted as 'closed'.
        return FeedbackStatsResponse.builder()
                .total(total)
                .pending(pending)
                .confirmed(confirmed)
                .assigned(assigned)
                .inProgress(inProgress)
                .resolved(resolved)
                .closed(closed)
                .rejected(0) // No direct mapping for REJECTED status
                .pendingReview(pendingReview)
                .pendingAssignment(pendingAssignment)
                .aiReviewing(aiReviewing)
                .build();
    }
}