package kr.co.accura.wtm.domain.approval.service;

import kr.co.accura.wbx.spring.common.NotFoundException;
import kr.co.accura.wtm.domain.approval.dto.*;
import kr.co.accura.wtm.domain.approval.entity.TtApproval;
import kr.co.accura.wtm.domain.approval.entity.TtApprovalComment;
import kr.co.accura.wtm.domain.approval.entity.TtApprovalLine;
import kr.co.accura.wtm.domain.approval.repository.TtApprovalCommentRepository;
import kr.co.accura.wtm.domain.approval.repository.TtApprovalLineRepository;
import kr.co.accura.wtm.domain.approval.repository.TtApprovalRepository;
import kr.co.accura.wtm.domain.timesheet.TimesheetStatus;
import kr.co.accura.wtm.domain.timesheet.entity.Timesheet;
import kr.co.accura.wtm.domain.timesheet.repository.TimesheetRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
@RequiredArgsConstructor
public class ApprovalService {

    private final TtApprovalRepository approvalRepository;
    private final TtApprovalLineRepository lineRepository;
    private final TtApprovalCommentRepository commentRepository;
    private final TimesheetRepository timesheetRepository;

    @Transactional(readOnly = true)
    public Page<ApprovalLineItemDto> getPending(Long approverId, Pageable pageable) {
        return lineRepository.findPendingByApproverId(approverId, pageable)
                .map(this::toLineDto);
    }

    public void approve(Long lineId, Long approverId, String comment) {
        TtApprovalLine line = lineRepository.findById(lineId)
                .orElseThrow(() -> new NotFoundException("결재 라인을 찾을 수 없습니다."));
        line.approve();
        lineRepository.save(line);

        TtApproval approval = line.getApproval();
        Timesheet ts = timesheetRepository.findById(approval.getTimesheetId())
                .orElseThrow(() -> new NotFoundException("Timesheet not found"));

        // Check for next pending approver
        var next = lineRepository.findNextPending(approval.getId(), line.getApprovalOrder());
        if (next.isPresent()) {
            ts.setStatus(TimesheetStatus.DL_APPROVED);
        } else {
            ts.setStatus(TimesheetStatus.APPROVED);
            approval.complete();
            approvalRepository.save(approval);
        }
        timesheetRepository.save(ts);

        if (comment != null && !comment.isBlank()) {
            addComment(approval, approverId, comment, "APPROVE");
        }
    }

    public void reject(Long lineId, Long approverId, String comment) {
        TtApprovalLine line = lineRepository.findById(lineId)
                .orElseThrow(() -> new NotFoundException("결재 라인을 찾을 수 없습니다."));
        line.reject();
        lineRepository.save(line);

        TtApproval approval = line.getApproval();
        approval.reject();
        approvalRepository.save(approval);

        Timesheet ts = timesheetRepository.findById(approval.getTimesheetId())
                .orElseThrow(() -> new NotFoundException("Timesheet not found"));
        ts.setStatus(TimesheetStatus.REJECTED);
        timesheetRepository.save(ts);

        if (comment != null && !comment.isBlank()) {
            addComment(approval, approverId, comment, "REJECT");
        }
    }

    public void batchApprove(List<Long> lineIds, Long approverId, String comment) {
        for (Long lineId : lineIds) {
            approve(lineId, approverId, comment);
        }
    }

    public void addComment(Long approvalId, Long userId, String comment) {
        TtApproval approval = approvalRepository.findById(approvalId)
                .orElseThrow(() -> new NotFoundException("결재를 찾을 수 없습니다."));
        addComment(approval, userId, comment, "COMMENT");
    }

    @Transactional(readOnly = true)
    public ApprovalDto getApprovalDetail(Long approvalId) {
        TtApproval approval = approvalRepository.findById(approvalId)
                .orElseThrow(() -> new NotFoundException("결재를 찾을 수 없습니다."));
        return toDto(approval);
    }

    @Transactional(readOnly = true)
    public Page<ApprovalLineItemDto> getHistory(Long approverId, Pageable pageable) {
        return lineRepository.findHistoryByApproverId(approverId, pageable)
                .map(this::toLineDto);
    }

    @Transactional(readOnly = true)
    public List<ApprovalCommentDto> getComments(Long approvalId) {
        return commentRepository.findByApproval_IdOrderByCreatedAtAsc(approvalId).stream()
                .map(this::toCommentDto)
                .toList();
    }

    // --- Helper methods ---

    private void addComment(TtApproval approval, Long userId, String comment, String action) {
        TtApprovalComment c = TtApprovalComment.builder()
                .approval(approval)
                .userId(userId)
                .comment(comment)
                .action(action)
                .build();
        commentRepository.save(c);
    }

    private ApprovalDto toDto(TtApproval a) {
        List<ApprovalLineItemDto> lines = a.getLines().stream().map(this::toLineDto).toList();
        List<ApprovalCommentDto> comments = a.getComments().stream().map(this::toCommentDto).toList();
        return new ApprovalDto(a.getId(), a.getTimesheetId(), a.getRequesterId(),
                a.getProjectId(), a.getStatus(), a.getSubmittedAt(), a.getCompletedAt(),
                lines, comments);
    }

    private ApprovalLineItemDto toLineDto(TtApprovalLine l) {
        return new ApprovalLineItemDto(l.getId(), l.getApproverId(),
                l.getApprovalOrder(), l.getRoleCode(), l.getStatus(), l.getActedAt());
    }

    private ApprovalCommentDto toCommentDto(TtApprovalComment c) {
        return new ApprovalCommentDto(c.getId(), c.getUserId(),
                c.getComment(), c.getAction(), c.getCreatedAt());
    }
}
