package com.zioinfo.hrm.uiws.message.service;

import com.zioinfo.hrm.uiws.common.UiwsApiException;
import com.zioinfo.hrm.uiws.common.UiwsCurrentUser;
import com.zioinfo.hrm.uiws.common.UiwsErrorCode;
import com.zioinfo.hrm.uiws.message.dto.MessageDtos.*;
import com.zioinfo.hrm.uiws.message.mapper.MessageMapper;
import com.zioinfo.hrm.uiws.message.model.UiwsMessage;
import com.zioinfo.hrm.uiws.message.model.UiwsMessageRcv;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 쪽지(모듈 03) 서비스 — 원본 com.urp.uiws.message.service.MessageService 를 MyBatis 로 변환 이식.
 * 로직(전송/답장, 보낸함, 개봉현황, 받은함, 개봉처리, 다중삭제, 미열람 배지)을 동등하게 보존한다.
 * 기본 조회기간 = 최근 1주일.
 */
@Service
@RequiredArgsConstructor
public class MessageService {

    private static final Set<String> VALID_RCV_TYPES = Set.of("RECV", "REF");
    private static final DateTimeFormatter DT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");

    private final MessageMapper mapper;

    // ------------------------------------------------------------------ 전송/답장
    @Transactional
    public MessageSendResponse send(MessageSendDto dto) {
        String actor = UiwsCurrentUser.id();
        LocalDateTime now = LocalDateTime.now();

        if (dto.replyToId() != null && !mapper.existsMessageById(dto.replyToId())) {
            throw new UiwsApiException(UiwsErrorCode.MESSAGE_NOT_FOUND, "답장 원본 쪽지를 찾을 수 없습니다.");
        }

        UiwsMessage m = new UiwsMessage();
        m.setSenderId(actor);
        m.setTitle(dto.title());
        m.setContent(dto.content());
        m.setRefWorklogId(dto.refWorklogId());
        m.setReplyToId(dto.replyToId());
        m.setSentAt(now);
        m.setSenderDelYn("N");
        m.setCreatedBy(actor);
        m.setCreatedAt(now);
        mapper.insertMessage(m); // useGeneratedKeys → m.messageId

        // 수신자: 동일 수신자 중복 제거(UNIQUE(message_id,receiver_id) 보호). 첫 등장 rcvType 채택.
        Set<String> seen = new LinkedHashSet<>();
        for (MessageReceiverDto r : dto.receivers()) {
            String receiverId = (r.receiverId() == null) ? null : r.receiverId().trim();
            String rcvType = (r.rcvType() == null) ? null : r.rcvType().trim().toUpperCase();
            if (receiverId == null || receiverId.isBlank()) {
                throw new UiwsApiException(UiwsErrorCode.INVALID_REQUEST, "수신자 ID가 비어 있습니다.");
            }
            if (!VALID_RCV_TYPES.contains(rcvType)) {
                throw new UiwsApiException(UiwsErrorCode.MESSAGE_RCV_TYPE_INVALID);
            }
            if (!seen.add(receiverId)) {
                continue;
            }
            UiwsMessageRcv rcv = new UiwsMessageRcv();
            rcv.setMessageId(m.getMessageId());
            rcv.setReceiverId(receiverId);
            rcv.setRcvType(rcvType);
            rcv.setReadYn("N");
            rcv.setReceiverDelYn("N");
            rcv.setCreatedBy(actor);
            rcv.setCreatedAt(now);
            mapper.insertRcv(rcv);
        }
        return new MessageSendResponse(m.getMessageId());
    }

    // ------------------------------------------------------------------ 보낸쪽지함
    @Transactional(readOnly = true)
    public SentPage listSent(LocalDate fromDate, LocalDate toDate, String titleKeyword, int page, int size) {
        LocalDateTime[] range = range(fromDate, toDate);
        String me = UiwsCurrentUser.id();
        String kw = blankToNull(titleKeyword);
        long total = mapper.countSent(me, range[0], range[1], kw);
        List<UiwsMessage> rows = mapper.searchSent(me, range[0], range[1], kw, size, page * size);

        List<Long> ids = rows.stream().map(UiwsMessage::getMessageId).toList();
        List<UiwsMessageRcv> rcvs = ids.isEmpty() ? List.of() : mapper.findRcvByMessageIds(ids);
        Map<Long, List<UiwsMessageRcv>> byMsg = rcvs.stream().collect(Collectors.groupingBy(UiwsMessageRcv::getMessageId));
        Map<String, String> names = userNames(rcvs.stream().map(UiwsMessageRcv::getReceiverId).toList());

        List<SentMessageDto> content = rows.stream().map(m -> {
            List<UiwsMessageRcv> list = byMsg.getOrDefault(m.getMessageId(), List.of());
            long openCount = list.stream().filter(r -> "Y".equals(r.getReadYn())).count();
            return new SentMessageDto(m.getMessageId(), m.getTitle(),
                    receiverSummary(list, names), openCount, list.size(), fmt(m.getSentAt()));
        }).toList();
        return new SentPage(content, total, page, size, totalPages(total, size));
    }

    // ------------------------------------------------------------------ 보낸쪽지 상세
    @Transactional(readOnly = true)
    public SentMessageDetailDto sentDetail(Long messageId) {
        String me = UiwsCurrentUser.id();
        UiwsMessage m = mapper.findMessageById(messageId);
        if (m == null) {
            throw new UiwsApiException(UiwsErrorCode.MESSAGE_NOT_FOUND);
        }
        if (!me.equals(m.getSenderId())) {
            throw new UiwsApiException(UiwsErrorCode.FORBIDDEN, "본인이 보낸 쪽지만 조회할 수 있습니다.");
        }
        List<UiwsMessageRcv> list = mapper.findRcvByMessageId(messageId);
        Map<String, String> names = userNames(list.stream().map(UiwsMessageRcv::getReceiverId).toList());

        List<ReceiverStatusDto> receivers = list.stream()
                .map(r -> new ReceiverStatusDto(
                        names.getOrDefault(r.getReceiverId(), r.getReceiverId()),
                        r.getRcvType(), r.getReadYn(), fmt(r.getReadAt())))
                .toList();
        long total = receivers.size();
        long open = list.stream().filter(r -> "Y".equals(r.getReadYn())).count();
        return new SentMessageDetailDto(m.getMessageId(), m.getTitle(), m.getContent(), m.getRefWorklogId(),
                fmt(m.getSentAt()), receivers, total, open, total - open);
    }

    // ------------------------------------------------------------------ 보낸쪽지 다중삭제
    @Transactional
    public void deleteSent(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return;
        }
        String me = UiwsCurrentUser.id();
        mapper.softDeleteSent(me, ids, me);
    }

    // ------------------------------------------------------------------ 미열람 수(배지)
    @Transactional(readOnly = true)
    public long unreadCount() {
        return mapper.countUnread(UiwsCurrentUser.id());
    }

    // ------------------------------------------------------------------ 받은쪽지함
    @Transactional(readOnly = true)
    public ReceivedPage listReceived(LocalDate fromDate, LocalDate toDate, String titleKeyword, int page, int size) {
        LocalDateTime[] range = range(fromDate, toDate);
        String me = UiwsCurrentUser.id();
        String kw = blankToNull(titleKeyword);
        long total = mapper.countReceived(me, range[0], range[1], kw);
        List<Map<String, Object>> rows = mapper.searchReceived(me, range[0], range[1], kw, size, page * size);
        Map<String, String> names = userNames(rows.stream().map(r -> str(r.get("senderId"))).toList());

        List<ReceivedMessageDto> content = rows.stream()
                .map(r -> new ReceivedMessageDto(
                        toLongObj(r.get("messageId")),
                        str(r.get("title")),
                        names.getOrDefault(str(r.get("senderId")), str(r.get("senderId"))),
                        fmt(toDt(r.get("sentAt"))),
                        str(r.get("readYn"))))
                .toList();
        return new ReceivedPage(content, total, page, size, totalPages(total, size));
    }

    // ------------------------------------------------------------------ 받은쪽지 상세(개봉처리)
    @Transactional
    public ReceivedMessageDetailDto receivedDetail(Long messageId) {
        String me = UiwsCurrentUser.id();
        UiwsMessageRcv rcv = mapper.findRcvByMessageAndReceiver(messageId, me);
        if (rcv == null || "Y".equals(rcv.getReceiverDelYn())) {
            throw new UiwsApiException(UiwsErrorCode.MESSAGE_NOT_FOUND, "받은 쪽지를 찾을 수 없습니다.");
        }
        UiwsMessage m = mapper.findMessageById(messageId);
        if (m == null) {
            throw new UiwsApiException(UiwsErrorCode.MESSAGE_NOT_FOUND);
        }
        LocalDateTime readAt = rcv.getReadAt();
        if (!"Y".equals(rcv.getReadYn())) {
            readAt = LocalDateTime.now();
            mapper.markRead(rcv.getRcvId(), me, readAt);
        }
        String senderNm = userNames(List.of(m.getSenderId())).getOrDefault(m.getSenderId(), m.getSenderId());
        return new ReceivedMessageDetailDto(m.getMessageId(), m.getTitle(), m.getContent(),
                senderNm, fmt(m.getSentAt()), fmt(readAt), m.getRefWorklogId());
    }

    // ------------------------------------------------------------------ 받은쪽지 다중삭제
    @Transactional
    public void deleteReceived(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return;
        }
        String me = UiwsCurrentUser.id();
        mapper.softDeleteReceived(me, ids, me);
    }

    // ================================================================== helpers

    private LocalDateTime[] range(LocalDate fromDate, LocalDate toDate) {
        LocalDate to = (toDate != null) ? toDate : LocalDate.now();
        LocalDate from = (fromDate != null) ? fromDate : to.minusWeeks(1);
        return new LocalDateTime[]{from.atStartOfDay(), to.atTime(LocalTime.MAX)};
    }

    private String receiverSummary(List<UiwsMessageRcv> list, Map<String, String> names) {
        if (list.isEmpty()) {
            return "";
        }
        String first = names.getOrDefault(list.get(0).getReceiverId(), list.get(0).getReceiverId());
        return list.size() == 1 ? first : first + " 외 " + (list.size() - 1) + "명";
    }

    private Map<String, String> userNames(List<String> userIds) {
        List<String> ids = userIds.stream().filter(s -> s != null && !s.isBlank()).distinct().toList();
        if (ids.isEmpty()) {
            return Map.of();
        }
        return mapper.findUserNames(ids).stream()
                .collect(Collectors.toMap(r -> r.get("username"), r -> r.get("fullName"), (a, b) -> a));
    }

    private static int totalPages(long total, int size) {
        return size <= 0 ? 0 : (int) ((total + size - 1) / size);
    }

    private static String blankToNull(String s) {
        return (s == null || s.isBlank()) ? null : s;
    }

    private static String str(Object o) {
        return o == null ? "" : o.toString();
    }

    private static Long toLongObj(Object o) {
        if (o == null) {
            return null;
        }
        return (o instanceof Number n) ? n.longValue() : Long.parseLong(o.toString());
    }

    private static LocalDateTime toDt(Object o) {
        if (o == null) {
            return null;
        }
        if (o instanceof LocalDateTime dt) {
            return dt;
        }
        if (o instanceof java.sql.Timestamp ts) {
            return ts.toLocalDateTime();
        }
        return null;
    }

    private static String fmt(LocalDateTime dt) {
        return dt != null ? dt.format(DT) : null;
    }

    // 페이지 응답(ERP ApiResponse 안에 그대로 직렬화 — content/totalElements/page/size/totalPages 보존)
    public record SentPage(List<SentMessageDto> content, long totalElements, int page, int size, int totalPages) {
    }

    public record ReceivedPage(List<ReceivedMessageDto> content, long totalElements, int page, int size, int totalPages) {
    }
}
