package com.unicorn.hgzero.meeting.biz.service;

import com.unicorn.hgzero.common.exception.BusinessException;
import com.unicorn.hgzero.common.exception.ErrorCode;
import com.unicorn.hgzero.meeting.biz.domain.Todo;
import com.unicorn.hgzero.meeting.biz.dto.TodoDTO;
import com.unicorn.hgzero.meeting.biz.usecase.in.todo.*;
import com.unicorn.hgzero.meeting.biz.usecase.out.TodoReader;
import com.unicorn.hgzero.meeting.biz.usecase.out.TodoWriter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * Todo Service
 * Todo 관련 모든 UseCase 구현
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class TodoService implements
        CreateTodoUseCase,
        UpdateTodoUseCase,
        CompleteTodoUseCase,
        CancelTodoUseCase,
        GetTodoUseCase {

    private final TodoReader todoReader;
    private final TodoWriter todoWriter;

    /**
     * Todo 생성
     */
    @Override
    @Transactional
    public Todo createTodo(CreateTodoCommand command) {
        log.info("Creating todo: {}", command.title());

        // Todo ID 생성
        String todoId = UUID.randomUUID().toString();

        // Todo 도메인 객체 생성
        Todo todo = Todo.builder()
                .todoId(todoId)
                .minutesId(command.minutesId())
                .meetingId(command.meetingId())
                .title(command.title())
                .description(command.description())
                .assigneeId(command.assigneeId())
                .dueDate(command.dueDate())
                .status("PENDING")
                .priority(command.priority() != null ? command.priority() : "MEDIUM")
                .build();

        // Todo 저장
        Todo savedTodo = todoWriter.save(todo);

        log.info("Todo created successfully: {}", savedTodo.getTodoId());
        return savedTodo;
    }

    /**
     * Todo 수정
     */
    @Override
    @Transactional
    public Todo updateTodo(UpdateTodoCommand command) {
        log.info("Updating todo: {}", command.todoId());

        // Todo 조회
        Todo todo = todoReader.findById(command.todoId())
                .orElseThrow(() -> new BusinessException(ErrorCode.ENTITY_NOT_FOUND));

        // 완료/취소 상태 검증
        if ("COMPLETED".equals(todo.getStatus()) || "CANCELLED".equals(todo.getStatus())) {
            throw new BusinessException(ErrorCode.INVALID_INPUT_VALUE);
        }

        // Todo 수정
        todo.update(
                command.title(),
                command.description(),
                command.assigneeId(),
                command.dueDate(),
                command.priority()
        );

        // 저장
        Todo updatedTodo = todoWriter.save(todo);

        log.info("Todo updated successfully: {}", command.todoId());
        return updatedTodo;
    }

    /**
     * Todo 완료
     */
    @Override
    @Transactional
    public Todo completeTodo(String todoId) {
        log.info("Completing todo: {}", todoId);

        // Todo 조회
        Todo todo = todoReader.findById(todoId)
                .orElseThrow(() -> new BusinessException(ErrorCode.ENTITY_NOT_FOUND));

        // 상태 검증
        if ("COMPLETED".equals(todo.getStatus())) {
            throw new BusinessException(ErrorCode.INVALID_INPUT_VALUE);
        }

        // Todo 완료
        todo.complete();

        // 저장
        Todo completedTodo = todoWriter.save(todo);

        log.info("Todo completed successfully: {}", todoId);
        return completedTodo;
    }

    /**
     * Todo 취소
     */
    @Override
    @Transactional
    public Todo cancelTodo(String todoId) {
        log.info("Cancelling todo: {}", todoId);

        // Todo 조회
        Todo todo = todoReader.findById(todoId)
                .orElseThrow(() -> new BusinessException(ErrorCode.ENTITY_NOT_FOUND));

        // 상태 검증
        if ("COMPLETED".equals(todo.getStatus()) || "CANCELLED".equals(todo.getStatus())) {
            throw new BusinessException(ErrorCode.INVALID_INPUT_VALUE);
        }

        // Todo 취소
        todo.cancel();

        // 저장
        Todo cancelledTodo = todoWriter.save(todo);

        log.info("Todo cancelled successfully: {}", todoId);
        return cancelledTodo;
    }

    /**
     * ID로 Todo 조회
     */
    @Override
    @Transactional(readOnly = true)
    public Todo getTodo(String todoId) {
        log.debug("Getting todo: {}", todoId);

        return todoReader.findById(todoId)
                .orElseThrow(() -> new BusinessException(ErrorCode.ENTITY_NOT_FOUND));
    }

    /**
     * 회의 ID로 Todo 목록 조회
     */
    @Override
    @Transactional(readOnly = true)
    public List<Todo> getTodosByMeeting(String meetingId) {
        log.debug("Getting todos by meeting: {}", meetingId);

        return todoReader.findByMeetingId(meetingId);
    }

    /**
     * 회의록 ID로 Todo 목록 조회
     */
    @Override
    @Transactional(readOnly = true)
    public List<Todo> getTodosByMinutes(String minutesId) {
        log.debug("Getting todos by minutes: {}", minutesId);

        return todoReader.findByMinutesId(minutesId);
    }

    /**
     * 담당자 ID로 Todo 목록 조회
     */
    @Override
    @Transactional(readOnly = true)
    public List<Todo> getTodosByAssignee(String assigneeId) {
        log.debug("Getting todos by assignee: {}", assigneeId);

        return todoReader.findByAssigneeId(assigneeId);
    }

    /**
     * 담당자 ID와 상태로 Todo 목록 조회
     */
    @Override
    @Transactional(readOnly = true)
    public List<Todo> getTodosByAssigneeAndStatus(String assigneeId, String status) {
        log.debug("Getting todos by assignee: {} and status: {}", assigneeId, status);

        return todoReader.findByAssigneeIdAndStatus(assigneeId, status);
    }

    /**
     * 담당자 ID와 마감일 범위로 Todo 목록 조회
     */
    @Override
    @Transactional(readOnly = true)
    public List<Todo> getTodosByAssigneeAndDueDateRange(String assigneeId, LocalDate startDate, LocalDate endDate) {
        log.debug("Getting todos by assignee: {} and due date range: {} ~ {}", assigneeId, startDate, endDate);

        return todoReader.findByAssigneeIdAndDueDateBetween(assigneeId, startDate, endDate);
    }

    /**
     * 담당자 ID로 Todo 목록 조회 (페이징, DTO 반환)
     */
    @Transactional(readOnly = true)
    public Page<TodoDTO> getTodoListByAssigneeId(String assigneeId, String status, Pageable pageable) {
        log.debug("Getting todo list by assigneeId: {}, status: {}", assigneeId, status);

        List<Todo> todoList;
        if (status != null && !status.isEmpty()) {
            todoList = todoReader.findByAssigneeIdAndStatus(assigneeId, status);
        } else {
            todoList = todoReader.findByAssigneeId(assigneeId);
        }
        
        // Todo를 TodoDTO로 변환
        List<TodoDTO> todoDTOList = todoList.stream()
                .map(this::convertToTodoDTO)
                .collect(Collectors.toList());

        // 페이징 처리 (임시로 전체 목록 반환)
        int start = (int) pageable.getOffset();
        int end = Math.min((start + pageable.getPageSize()), todoDTOList.size());
        List<TodoDTO> pageContent = todoDTOList.subList(start, end);

        return new PageImpl<>(pageContent, pageable, todoDTOList.size());
    }

    /**
     * Todo 도메인을 TodoDTO로 변환
     */
    private TodoDTO convertToTodoDTO(Todo todo) {
        return TodoDTO.builder()
                .todoId(todo.getTodoId())
                .minutesId(todo.getMinutesId())
                .content(todo.getDescription())
                .assignee(todo.getAssigneeId())
                .dueDate(todo.getDueDate())
                .priority(todo.getPriority())
                .status(todo.getStatus())
                .progress(todo.getProgress())
                .description(todo.getDescription())
                .createdBy(todo.getCreatedBy())
                .createdAt(todo.getCreatedAt())
                .updatedAt(todo.getLastModifiedAt())
                .completedAt(todo.getCompletedAt())
                // 추가 필드들은 임시로 기본값 설정
                .title(todo.getTitle())
                .assigneeId(todo.getAssigneeId())
                .assigneeName("임시 담당자명")
                .completedBy(todo.getCompletedBy())
                .minutesTitle("임시 회의록 제목")
                .meetingId(todo.getMeetingId())
                .meetingTitle("임시 회의 제목")
                .build();
    }
}
