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

import com.zioinfo.hrm.uiws.common.UiwsApiException;
import com.zioinfo.hrm.uiws.common.UiwsCurrentUser;
import com.zioinfo.hrm.uiws.common.UiwsDataScope;
import com.zioinfo.hrm.uiws.common.UiwsErrorCode;
import com.zioinfo.hrm.uiws.schedule.dto.ScheduleDtos.*;
import com.zioinfo.hrm.uiws.schedule.mapper.ScheduleMapper;
import com.zioinfo.hrm.uiws.schedule.model.UiwsSchedule;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 일정(모듈 02) 서비스 — 원본 com.urp.uiws.schedule.service.ScheduleService 를 MyBatis 변환 이식.
 * 달력(month/week/day)·전체목록·검색팝업·CRUD. 첨부 연계(attachmentIds).
 */
@Service
@RequiredArgsConstructor
public class ScheduleService {

    private static final String TYPE_PERSONAL = "PERSONAL";
    private static final String TYPE_DEPT = "DEPT";
    private static final DateTimeFormatter DT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");

    private final ScheduleMapper mapper;
    private final AttachmentService attachmentService;

    @Transactional(readOnly = true)
    public List<ScheduleDto> calendar(String type, String view, LocalDate baseDate, String deptId) {
        String t = normalizeType(type);
        LocalDate base = (baseDate != null) ? baseDate : LocalDate.now();
        LocalDate[] range = computeRange(view, base);
        LocalDateTime from = range[0].atStartOfDay();
        LocalDateTime to = range[1].atStartOfDay(); // 배타 상한

        String ownerId = TYPE_PERSONAL.equals(t) ? UiwsCurrentUser.id() : null;
        String deptFilter = TYPE_DEPT.equals(t) ? blankToNull(deptId) : null;

        return mapper.findInRange(from, to, t, ownerId, deptFilter).stream().map(this::toDto).toList();
    }

    @Transactional(readOnly = true)
    public SchedulePage all(LocalDate fromDate, LocalDate toDate, String type, int page, int size) {
        LocalDate to = (toDate != null) ? toDate : LocalDate.now();
        LocalDate from = (fromDate != null) ? fromDate : to.minusMonths(1);
        String t = (type == null || type.isBlank()) ? null : normalizeType(type);
        UiwsDataScope.Scope scope = UiwsDataScope.current();
        LocalDateTime f = from.atStartOfDay();
        LocalDateTime tt = to.plusDays(1).atStartOfDay();
        long total = mapper.countAll(f, tt, t, scope.all(), scope.ownerIds());
        List<ScheduleDto> content = mapper.searchAll(f, tt, t, scope.all(), scope.ownerIds(), size, page * size)
                .stream().map(this::toDto).toList();
        return new SchedulePage(content, total, page, size, totalPages(total, size));
    }

    @Transactional(readOnly = true)
    public List<ScheduleDto> search(String keyword) {
        UiwsDataScope.Scope scope = UiwsDataScope.current();
        return mapper.searchPopup(blankToNull(keyword), scope.all(), scope.ownerIds())
                .stream().map(this::toDto).toList();
    }

    @Transactional
    public ScheduleDto create(ScheduleSaveDto dto) {
        String actor = UiwsCurrentUser.id();
        UiwsSchedule s = new UiwsSchedule();
        applySave(s, dto);
        s.setOwnerId(actor);
        s.setCreatedBy(actor);
        s.setCreatedAt(LocalDateTime.now());
        mapper.insertSchedule(s);
        attachmentService.link(AttachmentService.REF_SCHEDULE, s.getScheduleId(), dto.attachmentIds());
        return toDto(s);
    }

    @Transactional(readOnly = true)
    public ScheduleDetailDto detail(Long id) {
        return toDetail(find(id));
    }

    @Transactional
    public ScheduleDetailDto update(Long id, ScheduleSaveDto dto) {
        String actor = UiwsCurrentUser.id();
        UiwsSchedule s = find(id);
        applySave(s, dto);
        s.setUpdatedBy(actor);
        s.setUpdatedAt(LocalDateTime.now());
        mapper.updateSchedule(s);
        attachmentService.link(AttachmentService.REF_SCHEDULE, s.getScheduleId(), dto.attachmentIds());
        return toDetail(s);
    }

    @Transactional
    public void delete(Long id) {
        if (!mapper.existsScheduleById(id)) {
            throw new UiwsApiException(UiwsErrorCode.SCHEDULE_NOT_FOUND);
        }
        attachmentService.deleteByRef(AttachmentService.REF_SCHEDULE, id);
        mapper.deleteScheduleById(id);
    }

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

    private void applySave(UiwsSchedule s, ScheduleSaveDto dto) {
        LocalDateTime start = parseDateTime(dto.startDt());
        LocalDateTime end = parseDateTime(dto.endDt());
        if (end.isBefore(start)) {
            throw new UiwsApiException(UiwsErrorCode.SCHEDULE_DT_INVALID, "종료 일시가 시작 일시보다 빠릅니다.");
        }
        s.setScheType(normalizeType(dto.scheType()));
        s.setTitle(dto.title());
        s.setScheGubunCd(blankToNull(dto.scheGubunCd()));
        s.setImportanceCd(blankToNull(dto.importanceCd()));
        s.setStartDt(start);
        s.setEndDt(end);
        s.setContent(dto.content());
        s.setDeptId(blankToNull(dto.deptId()));
        s.setChargerId(blankToNull(dto.chargerId()));
    }

    private ScheduleDto toDto(UiwsSchedule s) {
        return new ScheduleDto(s.getScheduleId(), s.getScheType(), s.getTitle(),
                s.getScheGubunCd(), s.getImportanceCd(), fmt(s.getStartDt()), fmt(s.getEndDt()),
                s.getOwnerId(), s.getDeptId());
    }

    private ScheduleDetailDto toDetail(UiwsSchedule s) {
        String chargerNm = null;
        if (s.getChargerId() != null) {
            chargerNm = userNames(List.of(s.getChargerId())).getOrDefault(s.getChargerId(), s.getChargerId());
        }
        List<AttachmentDto> attachments = attachmentService.list(AttachmentService.REF_SCHEDULE, s.getScheduleId());
        return new ScheduleDetailDto(s.getScheduleId(), s.getScheType(), s.getTitle(),
                s.getScheGubunCd(), s.getImportanceCd(), fmt(s.getStartDt()), fmt(s.getEndDt()),
                s.getOwnerId(), s.getDeptId(), s.getContent(), s.getChargerId(), chargerNm, attachments);
    }

    private UiwsSchedule find(Long id) {
        UiwsSchedule s = mapper.findScheduleById(id);
        if (s == null) {
            throw new UiwsApiException(UiwsErrorCode.SCHEDULE_NOT_FOUND);
        }
        return s;
    }

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

    private LocalDate[] computeRange(String view, LocalDate base) {
        String v = (view == null || view.isBlank()) ? "month" : view.trim().toLowerCase();
        return switch (v) {
            case "day" -> new LocalDate[]{base, base.plusDays(1)};
            case "week" -> {
                LocalDate start = base.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
                yield new LocalDate[]{start, start.plusWeeks(1)};
            }
            case "month" -> {
                LocalDate start = base.withDayOfMonth(1);
                yield new LocalDate[]{start, start.plusMonths(1)};
            }
            default -> throw new UiwsApiException(UiwsErrorCode.INVALID_REQUEST,
                    "view 는 month/week/day 중 하나여야 합니다.");
        };
    }

    private String normalizeType(String type) {
        String t = (type == null) ? "" : type.trim().toUpperCase();
        if (!TYPE_PERSONAL.equals(t) && !TYPE_DEPT.equals(t)) {
            throw new UiwsApiException(UiwsErrorCode.INVALID_REQUEST, "type 은 PERSONAL/DEPT 중 하나여야 합니다.");
        }
        return t;
    }

    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 fmt(LocalDateTime dt) {
        return dt != null ? dt.format(DT) : null;
    }

    private static LocalDateTime parseDateTime(String s) {
        if (s == null || s.isBlank()) {
            throw new UiwsApiException(UiwsErrorCode.INVALID_REQUEST, "일시 값이 비어 있습니다.");
        }
        try {
            if (s.length() <= 10) {
                return LocalDate.parse(s).atStartOfDay();
            }
            return LocalDateTime.parse(s);
        } catch (Exception e) {
            throw new UiwsApiException(UiwsErrorCode.INVALID_REQUEST,
                    "일시 형식이 올바르지 않습니다(yyyy-MM-ddTHH:mm:ss).");
        }
    }
}
