package com.dne.ems.service.impl;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
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.scheduling.annotation.Async;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

import com.dne.ems.dto.AttachmentDTO;
import com.dne.ems.dto.TaskApprovalRequest;
import com.dne.ems.dto.TaskAssignmentRequest;
import com.dne.ems.dto.TaskCreationRequest;
import com.dne.ems.dto.TaskDetailDTO;
import com.dne.ems.dto.TaskFromFeedbackRequest;
import com.dne.ems.dto.TaskHistoryDTO;
import com.dne.ems.dto.TaskSummaryDTO;
import com.dne.ems.event.TaskReadyForAssignmentEvent;
import com.dne.ems.exception.InvalidOperationException;
import com.dne.ems.exception.ResourceNotFoundException;
import com.dne.ems.model.Attachment;
import com.dne.ems.model.Feedback;
import com.dne.ems.model.Task;
import com.dne.ems.model.TaskHistory;
import com.dne.ems.model.TaskSubmission;
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.model.enums.TaskStatus;
import com.dne.ems.repository.AttachmentRepository;
import com.dne.ems.repository.FeedbackRepository;
import com.dne.ems.repository.TaskHistoryRepository;
import com.dne.ems.repository.TaskRepository;
import com.dne.ems.repository.TaskSubmissionRepository;
import com.dne.ems.repository.UserAccountRepository;
import com.dne.ems.security.CustomUserDetails;
import com.dne.ems.service.OperationLogService;
import com.dne.ems.service.TaskManagementService;
import com.dne.ems.service.pathfinding.AStarService;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;

@Service
@RequiredArgsConstructor
@Slf4j
public class TaskManagementServiceImpl implements TaskManagementService {

    private final TaskRepository taskRepository;
    private final UserAccountRepository userAccountRepository;
    private final TaskHistoryRepository taskHistoryRepository;
    private final ApplicationEventPublisher eventPublisher;
    private final FeedbackRepository feedbackRepository;
    private final TaskSubmissionRepository taskSubmissionRepository;
    private final AttachmentRepository attachmentRepository;
    private final OperationLogService operationLogService;
    @SuppressWarnings("unused")
    private final AStarService aStarService;

    @Value("${app.base.url}")
    private String appBaseUrl;

    @Override
    public Page<TaskSummaryDTO> getTasks(
        TaskStatus status, Long assigneeId, SeverityLevel severity,
        PollutionType pollutionType, LocalDate startDate, LocalDate endDate, Pageable pageable
    ) {
        List<Task> allTasks = taskRepository.findAll();

        List<Task> filteredTasks = allTasks.stream()
            .filter(task -> status == null || task.getStatus() == status)
            .filter(task -> assigneeId == null || (task.getAssignee() != null && task.getAssignee().getId().equals(assigneeId)))
            .filter(task -> severity == null || (task.getSeverityLevel() == severity || (task.getFeedback() != null && task.getFeedback().getSeverityLevel() == severity)))
            .filter(task -> pollutionType == null || (task.getPollutionType() == pollutionType || (task.getFeedback() != null && task.getFeedback().getPollutionType() == pollutionType)))
            .filter(task -> startDate == null || !task.getCreatedAt().toLocalDate().isBefore(startDate))
            .filter(task -> endDate == null || !task.getCreatedAt().toLocalDate().isAfter(endDate))
            .collect(Collectors.toList());

        // Manual sorting
        if (pageable.getSort().isSorted()) {
            // This is a simplified sort implementation. A full implementation would parse the Sort object.
            // For now, we just support the default createdAt descending.
            if (pageable.getSort().getOrderFor("createdAt") != null && pageable.getSort().getOrderFor("createdAt").isDescending()) {
                filteredTasks.sort((t1, t2) -> t2.getCreatedAt().compareTo(t1.getCreatedAt()));
            }
        } else {
            // Default sort
            filteredTasks.sort((t1, t2) -> t2.getCreatedAt().compareTo(t1.getCreatedAt()));
        }

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

        Page<Task> taskPage = new PageImpl<>(pageContent, pageable, filteredTasks.size());
        return taskPage.map(this::convertToSummaryDto);
    }

    @Override
    public TaskSummaryDTO assignTask(Long taskId, TaskAssignmentRequest request) {
        Task task = taskRepository.findById(taskId).orElseThrow(() -> new ResourceNotFoundException("Task not found"));
        if (task.getStatus() != TaskStatus.PENDING_ASSIGNMENT && task.getStatus() != TaskStatus.ASSIGNED) {
            throw new InvalidOperationException("Task must be pending or already assigned.");
        }
        UserAccount assignee = userAccountRepository.findById(request.assigneeId()).orElseThrow(() -> new ResourceNotFoundException("Assignee not found"));
        if (assignee.getRole() != Role.GRID_WORKER) {
            throw new InvalidOperationException("Cannot assign tasks to non-grid workers.");
        }
        TaskStatus oldStatus = task.getStatus();
        task.setAssignee(assignee);
        task.setStatus(TaskStatus.ASSIGNED);
        task.setAssignedAt(LocalDateTime.now());
        Task updatedTask = taskRepository.save(task);
        logTaskHistory(updatedTask, oldStatus, updatedTask.getStatus(), "Task assigned to " + assignee.getName());
        
        operationLogService.recordOperation(
            com.dne.ems.model.enums.OperationType.ASSIGN_TASK,
            "将任务 (ID: " + taskId + ") 分配给 " + assignee.getName(),
            taskId.toString(),
            "Task"
        );
        
        return convertToSummaryDto(updatedTask);
    }

    @Override
    public TaskDetailDTO getTaskDetails(Long taskId) {
        return taskRepository.findById(taskId).map(this::convertToDetailDto).orElseThrow(() -> new ResourceNotFoundException("Task not found"));
    }

    @Override
    public TaskDetailDTO reviewTask(Long taskId, TaskApprovalRequest request) {
        Task task = taskRepository.findById(taskId).orElseThrow(() -> new ResourceNotFoundException("Task not found"));
        if (task.getStatus() != TaskStatus.SUBMITTED) {
            throw new InvalidOperationException("Task must be SUBMITTED to be reviewed.");
        }
        TaskStatus oldStatus = task.getStatus();
        task.setStatus(request.approved() ? TaskStatus.COMPLETED : TaskStatus.ASSIGNED);
        if (request.approved()) {
            task.setCompletedAt(LocalDateTime.now());
            Feedback feedback = task.getFeedback();
            if (feedback != null) {
                log.info("Task {} approved. Updating associated feedback {} from {} to PROCESSED.", 
                         task.getId(), feedback.getId(), feedback.getStatus());
                feedback.setStatus(FeedbackStatus.PROCESSED);
                feedbackRepository.save(feedback);
            }
        }
        Task updatedTask = taskRepository.save(task);
        logTaskHistory(updatedTask, oldStatus, updatedTask.getStatus(), request.comments());
        return convertToDetailDto(updatedTask);
    }
    
    @Override
    public TaskDetailDTO cancelTask(Long taskId) {
        Task task = taskRepository.findById(taskId).orElseThrow(() -> new ResourceNotFoundException("Task not found"));
        if (task.getStatus() == TaskStatus.COMPLETED || task.getStatus() == TaskStatus.CANCELLED) {
            throw new InvalidOperationException("Cannot cancel a task that is already completed or cancelled.");
        }
        TaskStatus oldStatus = task.getStatus();
        task.setStatus(TaskStatus.CANCELLED);
        Task updatedTask = taskRepository.save(task);
        logTaskHistory(updatedTask, oldStatus, updatedTask.getStatus(), "Task manually cancelled.");
        return convertToDetailDto(updatedTask);
    }

    @Override
    public List<TaskHistoryDTO> getTaskHistory(Long taskId) {
        if (!taskRepository.existsById(taskId)) throw new ResourceNotFoundException("Task not found");
        return taskHistoryRepository.findByTaskIdOrderByChangedAtDesc(taskId).stream().map(this::convertToHistoryDto).collect(Collectors.toList());
    }

    @Override
    public TaskDetailDTO createTask(TaskCreationRequest request) {
        Task task = new Task();
        task.setTitle(request.title());
        task.setDescription(request.description());
        task.setPollutionType(request.pollutionType());
        task.setSeverityLevel(request.severityLevel());
        task.setLatitude(request.location().latitude());
        task.setLongitude(request.location().longitude());
        task.setTextAddress(request.location().textAddress());
        task.setGridX(request.location().gridX());
        task.setGridY(request.location().gridY());
        task.setStatus(TaskStatus.PENDING_ASSIGNMENT);
        task.setCreatedBy(getCurrentUser());
        task.setCreatedAt(LocalDateTime.now());
        Task createdTask = taskRepository.save(task);
        logTaskHistory(createdTask, null, createdTask.getStatus(), "Task created manually.");
        eventPublisher.publishEvent(new TaskReadyForAssignmentEvent(this, createdTask));
        return convertToDetailDto(createdTask);
    }

    /**
     * {@inheritDoc}
     *
     * <p>实现细节：
     * <ol>
     *   <li>查询状态为PENDING_REVIEW的反馈</li>
     *   <li>返回未处理的反馈列表</li>
     * </ol>
     */
    @Override
    public List<Feedback> getPendingFeedback() {
        return feedbackRepository.findByStatus(FeedbackStatus.PENDING_REVIEW);
    }

    @Override
    public TaskDetailDTO createTaskFromFeedback(Long feedbackId, TaskFromFeedbackRequest request) {
        Feedback feedback = feedbackRepository.findById(feedbackId).orElseThrow(() -> new ResourceNotFoundException("Feedback not found"));
        if (feedback.getStatus() != FeedbackStatus.PENDING_REVIEW) {
            throw new InvalidOperationException("Feedback must be in PENDING_REVIEW state.");
        }
        Task task = new Task();
        task.setTitle(request.getTitle());
        task.setDescription(feedback.getDescription());
        task.setPollutionType(feedback.getPollutionType());
        task.setSeverityLevel(request.getSeverityLevel());
        task.setLatitude(feedback.getLatitude());
        task.setLongitude(feedback.getLongitude());
        task.setTextAddress(feedback.getTextAddress());
        task.setGridX(feedback.getGridX());
        task.setGridY(feedback.getGridY());
        task.setStatus(TaskStatus.PENDING_ASSIGNMENT);
        task.setFeedback(feedback);
        task.setCreatedBy(getCurrentUser());
        task.setCreatedAt(LocalDateTime.now());
        Task createdTask = taskRepository.save(task);
        feedback.setStatus(FeedbackStatus.PROCESSED);
        feedbackRepository.save(feedback);
        logTaskHistory(createdTask, null, createdTask.getStatus(), "Task created from feedback ID: " + feedbackId);
        eventPublisher.publishEvent(new TaskReadyForAssignmentEvent(this, createdTask));
        return convertToDetailDto(createdTask);
    }

    /**
     * {@inheritDoc}
     *
     * <p>实现细节：
     * <ol>
     *   <li>异步处理任务分配事件</li>
     *   <li>触发智能任务分配流程</li>
     * </ol>
     *
     * <p>事务处理：
     * <ul>
     *   <li>在事务提交后执行</li>
     *   <li>确保任务数据已持久化</li>
     * </ul>
     */
    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    @Override
    public void handleTaskReadyForAssignment(TaskReadyForAssignmentEvent event) {
        log.info("Received task ready for assignment event for task id: {}", event.getTask().getId());
        intelligentAssignTask(event.getTask());
    }

    private void intelligentAssignTask(Task task) {
        // Dummy implementation for now
        log.warn("Intelligent task assignment not implemented. Task {} needs manual assignment.", task.getId());
    }
    
    private UserAccount getCurrentUser() {
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof CustomUserDetails customUserDetails) {
            return customUserDetails.getUserAccount();
        }
        // This should not happen in a secured context
        return null;
    }

    private void logTaskHistory(Task task, TaskStatus oldStatus, TaskStatus newStatus, String comments) {
        TaskHistory history = TaskHistory.builder()
                .task(task)
                .oldStatus(oldStatus)
                .newStatus(newStatus)
                .comments(comments)
                .changedAt(LocalDateTime.now())
                .changedBy(getCurrentUser())
                .build();
        taskHistoryRepository.save(history);
    }

    private TaskSummaryDTO convertToSummaryDto(Task task) {
        Feedback feedback = task.getFeedback();
        SeverityLevel severity = feedback != null ? feedback.getSeverityLevel() : task.getSeverityLevel();
        String imageUrl = "";
        // Note: This is a simplified representation. A real implementation might need to
        // query attachments associated with the feedback. For now, we assume no image
        // or a placeholder logic is sufficient for the summary.
        if (feedback != null) {
            List<Attachment> attachments = attachmentRepository.findByFeedback(feedback);
            if (attachments != null && !attachments.isEmpty()) {
                imageUrl = appBaseUrl + "/api/files/view/" + attachments.get(0).getStoredFileName();
            }
        }
        
        return new TaskSummaryDTO(
                task.getId(),
                task.getTitle(),
                task.getDescription(),
                task.getStatus(),
                task.getAssignee() != null ? task.getAssignee().getName() : "N/A",
                task.getCreatedAt(),
                task.getTextAddress(),
                imageUrl,
                severity
        );
    }

    private TaskDetailDTO convertToDetailDto(Task task) {
        @SuppressWarnings("unused")
        UserAccount creator = task.getCreatedBy();
        UserAccount assignee = task.getAssignee();
        Feedback feedback = task.getFeedback();

        // Assignee DTO
        TaskDetailDTO.AssigneeDTO assigneeDto = null;
        if (assignee != null) {
            assigneeDto = new TaskDetailDTO.AssigneeDTO(assignee.getId(), assignee.getName(), assignee.getPhone());
        }

        // Submission DTO
        List<TaskSubmission> submissions = taskSubmissionRepository.findByTaskId(task.getId());
        List<TaskDetailDTO.SubmissionInfoDTO> submissionInfo = submissions.stream()
                .map(s -> {
                    List<Attachment> submissionAttachments = attachmentRepository.findByTaskSubmission(s);
                    List<AttachmentDTO> attachmentDTOs = submissionAttachments.stream()
                            .map(a -> new AttachmentDTO(
                                a.getId(), 
                                a.getFileName(), 
                                a.getFileType(), 
                                appBaseUrl + "/api/files/view/" + a.getStoredFileName(),
                                a.getUploadDate()
                            ))
                            .collect(Collectors.toList());
                    return new TaskDetailDTO.SubmissionInfoDTO(s.getNotes(), attachmentDTOs);
                })
                .collect(Collectors.toList());
        
        // Feedback DTO
        TaskDetailDTO.FeedbackDTO feedbackDto = null;
        if (feedback != null) {
            List<String> feedbackImageUrls = attachmentRepository.findByFeedback(feedback).stream()
                .map(a -> appBaseUrl + "/api/files/view/" + a.getStoredFileName())
                .collect(Collectors.toList());
            
            feedbackDto = new TaskDetailDTO.FeedbackDTO(
                feedback.getId(), 
                feedback.getEventId(),
                feedback.getTitle(),
                feedback.getDescription(),
                feedback.getSeverityLevel(),
                feedback.getTextAddress(),
                feedback.getSubmitterId(),
                null, // Submitter name can be fetched if needed
                feedback.getCreatedAt(),
                feedbackImageUrls
            );
        }

        List<TaskHistoryDTO> historyDtos = taskHistoryRepository.findByTaskIdOrderByChangedAtDesc(task.getId())
            .stream()
            .map(this::convertToHistoryDto)
            .collect(Collectors.toList());

        return new TaskDetailDTO(
                task.getId(),
                feedbackDto,
                task.getTitle(),
                task.getDescription(),
                task.getSeverityLevel(),
                task.getPollutionType(),
                task.getTextAddress(),
                task.getGridX(),
                task.getGridY(),
                task.getStatus(),
                assigneeDto,
                task.getAssignedAt(),
                task.getCompletedAt(),
                historyDtos,
                submissionInfo.isEmpty() ? null : submissionInfo.get(0) // Assuming one submission for detail view
        );
    }

    private TaskHistoryDTO convertToHistoryDto(TaskHistory history) {
        UserAccount changedBy = history.getChangedBy();
        String changedByName = (changedBy != null) ? changedBy.getName() : "System";
        TaskHistoryDTO.UserDTO userDto = (changedBy != null) ? new TaskHistoryDTO.UserDTO(changedBy.getId(), changedByName) : new TaskHistoryDTO.UserDTO(null, changedByName);

        return new TaskHistoryDTO(
                history.getId(),
                history.getOldStatus(),
                history.getNewStatus(),
                history.getComments(),
                history.getChangedAt(),
                userDto
        );
    }
    
    @Override
    public TaskDetailDTO approveTask(Long taskId) {
        Task task = taskRepository.findById(taskId)
                .orElseThrow(() -> new ResourceNotFoundException("Task not found with id: " + taskId));

        if (task.getStatus() != TaskStatus.SUBMITTED) {
            throw new InvalidOperationException("Only submitted tasks can be approved.");
        }

        task.setStatus(TaskStatus.COMPLETED);
        task.setCompletedAt(LocalDateTime.now());

        Task savedTask = taskRepository.save(task);

        logTaskHistory(savedTask, TaskStatus.SUBMITTED, TaskStatus.COMPLETED, "Task approved and completed.");

        operationLogService.recordOperation(
            com.dne.ems.model.enums.OperationType.APPROVE_TASK,
            "批准了任务 (ID: " + taskId + ")",
            taskId.toString(),
            "Task"
        );

        return convertToDetailDto(savedTask);
    }
    
    @Override
    public TaskDetailDTO rejectTask(Long taskId, String reason) {
        Task task = taskRepository.findById(taskId)
                .orElseThrow(() -> new ResourceNotFoundException("Task not found with id: " + taskId));

        if (task.getStatus() != TaskStatus.SUBMITTED) {
            throw new InvalidOperationException("Only submitted tasks can be rejected.");
        }
        
        TaskStatus oldStatus = task.getStatus();

        task.setStatus(TaskStatus.IN_PROGRESS); // or ASSIGNED, depends on business logic
        Task savedTask = taskRepository.save(task);

        logTaskHistory(savedTask, oldStatus, task.getStatus(), "Task rejected. Reason: " + reason);

        operationLogService.recordOperation(
            com.dne.ems.model.enums.OperationType.REJECT_TASK,
            "拒绝了任务 (ID: " + taskId + "), 理由: " + reason,
            taskId.toString(),
            "Task"
        );

        return convertToDetailDto(savedTask);
    }

}