package com.ga.api.service.approval;

import com.ga.common.adapter.bank.BankTransferAdapter;
import com.ga.common.adapter.bank.BankTransferRequest;
import com.ga.common.adapter.bank.BankTransferResponse;
import com.ga.common.adapter.bank.BankTransferStatus;
import com.ga.common.adapter.message.MessageAdapter;
import com.ga.common.adapter.message.MessageChannel;
import com.ga.common.adapter.message.MessageRequest;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.common.util.SecurityUtil;
import com.ga.core.mapper.approval.*;
import com.ga.core.mapper.notice.UserNotificationMapper;
import com.ga.core.mapper.user.UserMapper;
import com.ga.core.mapper.settle.CommissionDisputeMapper;
import com.ga.core.mapper.settle.IncentivePaymentMapper;
import com.ga.core.mapper.settle.SettleMasterMapper;
import com.ga.core.mapper.withdraw.WithdrawRequestMapper;
import com.ga.core.vo.approval.*;
import com.ga.core.vo.notice.NotificationRefType;
import com.ga.core.vo.withdraw.WithdrawRequestVO;
// Note: ApprovalLineVO kept for createLine/updateLine methods
import com.ga.core.vo.notice.UserNotificationVO;
import com.ga.core.vo.settle.SettleStatus;
import com.ga.core.vo.user.UserContactBrief;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class ApprovalService {

    private final ApprovalLineMapper lineMapper;
    private final ApprovalLineStepMapper stepMapper;
    private final ApprovalRequestMapper requestMapper;
    private final ApprovalHistoryMapper historyMapper;
    private final UserNotificationMapper notificationMapper;
    private final UserMapper userMapper;

    // 콜백 대상 도메인 Mapper
    private final WithdrawRequestMapper withdrawMapper;
    private final SettleMasterMapper settleMasterMapper;
    private final CommissionDisputeMapper disputeMapper;
    private final IncentivePaymentMapper incentivePaymentMapper;

    // P6 외부연동 어댑터 — 실제 구현은 ga-external-adapter 모듈 (현재 Mock)
    private final BankTransferAdapter bankTransferAdapter;
    private final MessageAdapter messageAdapter;

    // ── Line ──────────────────────────────────────────────────────────────────

    public PageResponse<ApprovalLineResp> listLines(ApprovalLineSearchParam param) {
        param.startPage();
        return PageResponse.of(lineMapper.selectList(param));
    }

    public ApprovalLineResp detailLine(Long lineId) {
        ApprovalLineResp resp = lineMapper.selectById(lineId);
        if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
        return resp;
    }

    public List<ApprovalLineStepResp> stepsOfLine(Long lineId) {
        return stepMapper.selectByLine(lineId);
    }

    @Transactional
    public Long createLine(ApprovalLineSaveReq req) {
        ApprovalLineVO vo = new ApprovalLineVO();
        vo.setLineCode(req.getLineCode());
        vo.setLineName(req.getLineName());
        vo.setMaxStep(req.getMaxStep());
        vo.setTargetType(req.getTargetType());
        vo.setIsActive(true);
        vo.setCreatedBy(SecurityUtil.getCurrentUserId());
        lineMapper.insert(vo);
        return vo.getLineId();
    }

    @Transactional
    public void updateLine(Long lineId, ApprovalLineSaveReq req) {
        ApprovalLineResp existing = lineMapper.selectById(lineId);
        if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
        ApprovalLineVO vo = new ApprovalLineVO();
        vo.setLineId(lineId);
        vo.setLineCode(req.getLineCode());
        vo.setLineName(req.getLineName());
        vo.setMaxStep(req.getMaxStep());
        vo.setTargetType(req.getTargetType());
        lineMapper.update(vo);
    }

    @Transactional
    public void addStep(ApprovalLineStepSaveReq req) {
        ApprovalLineStepVO vo = new ApprovalLineStepVO();
        vo.setLineId(req.getLineId());
        vo.setStepNo(req.getStepNo());
        vo.setRoleCode(req.getRoleCode());
        stepMapper.insert(vo);
    }

    // ── Request ───────────────────────────────────────────────────────────────

    public PageResponse<ApprovalRequestResp> listRequests(ApprovalRequestSearchParam param) {
        param.startPage();
        return PageResponse.of(requestMapper.selectList(param));
    }

    /** 내가 상신한 결재 요청 */
    public PageResponse<ApprovalRequestResp> myRequests(ApprovalRequestSearchParam param) {
        param.setRequestedBy(SecurityUtil.getCurrentUserId());
        param.startPage();
        return PageResponse.of(requestMapper.selectList(param));
    }

    /** 진행 중(PENDING) 결재 요청 */
    public PageResponse<ApprovalRequestResp> pendingRequests(ApprovalRequestSearchParam param) {
        param.setStatus("PENDING");
        param.startPage();
        return PageResponse.of(requestMapper.selectList(param));
    }

    public ApprovalRequestResp detailRequest(Long requestId) {
        ApprovalRequestResp resp = requestMapper.selectById(requestId);
        if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
        return resp;
    }

    public List<ApprovalHistoryResp> historyOfRequest(Long requestId) {
        return historyMapper.selectByRequest(requestId);
    }

    /**
     * 결재 요청 생성 — PENDING status=1, 첫 결재자에게 알림 발송.
     */
    @Transactional
    public Long createRequest(ApprovalRequestSaveReq req) {
        // 결재선 존재 확인
        ApprovalLineResp line = lineMapper.selectById(req.getLineId());
        if (line == null) throw new BizException(ErrorCode.NOT_FOUND, "결재선을 찾을 수 없습니다");

        ApprovalRequestVO vo = new ApprovalRequestVO();
        vo.setTargetType(req.getTargetType());
        vo.setTargetId(req.getTargetId());
        vo.setLineId(req.getLineId());
        vo.setRequestedBy(req.getRequestedBy());
        vo.setRequestedAt(LocalDateTime.now());
        vo.setCurrentStep(1);
        vo.setStatus(ApprovalStatus.PENDING.name());
        requestMapper.insert(vo);

        // 1단계 결재자에게 알림
        sendStepNotification(vo.getRequestId(), req.getLineId(), 1, req.getTargetType());
        return vo.getRequestId();
    }

    /**
     * 결재 단계 진행 — history INSERT + step advance + 최종 처리.
     * @param requestId  결재 요청 PK
     * @param approverId 결재자 user_id
     * @param action     APPROVE / REJECT
     * @param comment    코멘트
     */
    @Transactional
    public void advance(Long requestId, Long approverId, String action, String comment) {
        ApprovalRequestResp reqResp = requestMapper.selectById(requestId);
        if (reqResp == null) throw new BizException(ErrorCode.NOT_FOUND);
        if (!ApprovalStatus.PENDING.name().equals(reqResp.getStatus())) {
            throw new BizException(ErrorCode.INVALID_STATUS, "진행 중인 결재 요청이 아닙니다");
        }

        // 1. 결재 이력 INSERT
        ApprovalHistoryVO history = new ApprovalHistoryVO();
        history.setRequestId(requestId);
        history.setStepNo(reqResp.getCurrentStep());
        history.setApproverId(approverId);
        history.setAction(action);
        history.setComment(comment);
        history.setProcessedAt(LocalDateTime.now());
        historyMapper.insert(history);

        // 2. advanceStep — DB에서 current_step 갱신
        requestMapper.advanceStep(requestId, approverId, action, comment);

        // 3. REJECT → 종료
        if (ApprovalAction.REJECT.name().equals(action)) {
            requestMapper.updateStatus(requestId, ApprovalStatus.REJECTED.name());
            log.info("결재 요청 반려: requestId={}", requestId);
            return;
        }

        // 4. APPROVE 처리
        if (ApprovalAction.APPROVE.name().equals(action)) {
            int maxStep = reqResp.getMaxStep() != null ? reqResp.getMaxStep() : 1;
            int currentStep = reqResp.getCurrentStep();

            if (currentStep < maxStep) {
                // 다음 단계 알림
                sendStepNotification(requestId, reqResp.getLineId(), currentStep + 1, reqResp.getTargetType());
            } else {
                // 최종 승인 — 콜백 처리
                requestMapper.updateStatus(requestId, ApprovalStatus.APPROVED.name());
                handleApprovalCallback(reqResp.getTargetType(), reqResp.getTargetId());
            }
        }
    }

    /**
     * 결재 취소.
     */
    @Transactional
    public void cancel(Long requestId) {
        ApprovalRequestResp reqResp = requestMapper.selectById(requestId);
        if (reqResp == null) throw new BizException(ErrorCode.NOT_FOUND);
        if (!ApprovalStatus.PENDING.name().equals(reqResp.getStatus())) {
            throw new BizException(ErrorCode.INVALID_STATUS, "진행 중인 결재만 취소 가능합니다");
        }
        requestMapper.cancel(requestId);
    }

    // ── Callback ──────────────────────────────────────────────────────────────

    /**
     * 최종 승인 후 target_type 별 도메인 후처리.
     * - WITHDRAW  → withdraw_request.status=APPROVED (펌뱅킹 SENT는 P5)
     * - SETTLE    → settle_master.status=CONFIRMED
     * - DISPUTE   → commission_dispute.status=APPROVED (정정 생성은 P5)
     * - INCENTIVE → incentive_payment.status=CONFIRMED
     */
    private void handleApprovalCallback(String targetType, Long targetId) {
        if (targetType == null || targetId == null) return;
        try {
            switch (targetType) {
                case "WITHDRAW" -> {
                    withdrawMapper.updateStatus(targetId, "APPROVED", null);
                    log.info("출금신청 승인 완료: requestId={}", targetId);
                    requestBankTransfer(targetId);
                }
                case "SETTLE" -> {
                    settleMasterMapper.updateStatus(targetId, SettleStatus.CONFIRMED.name(),
                            SecurityUtil.getCurrentUserId());
                    log.info("정산 확정 완료: settleId={}", targetId);
                }
                case "DISPUTE" -> {
                    disputeMapper.resolve(targetId, "APPROVED", null, null, null,
                            SecurityUtil.getCurrentUserId());
                    log.info("이의신청 승인 완료: disputeId={}", targetId);
                }
                case "INCENTIVE" -> {
                    incentivePaymentMapper.updateStatus(targetId, "CONFIRMED",
                            SecurityUtil.getCurrentUserId());
                    log.info("시책 확정 완료: incentivePayId={}", targetId);
                }
                default -> log.warn("알 수 없는 결재 대상 타입: targetType={}", targetType);
            }
        } catch (Exception e) {
            log.error("결재 콜백 실패: targetType={}, targetId={}", targetType, targetId, e);
            throw new BizException(ErrorCode.INTERNAL_ERROR, "결재 후처리 중 오류가 발생했습니다");
        }
    }

    /** 다음 결재 단계 담당자에게 알림 발송 (DB + 외부채널). 담당자 user는 step.roleCode 매핑. */
    private void sendStepNotification(Long requestId, Long lineId, int stepNo, String targetType) {
        try {
            ApprovalLineStepVO step = stepMapper.selectByLineAndStep(lineId, stepNo);
            if (step == null) return;

            // step.roleCode → user/phone 1차 매핑. 없으면 userId=0L 으로 fallback (히스토리 보존).
            UserContactBrief contact = step.getRoleCode() != null
                    ? userMapper.selectFirstActiveContactByRoleCode(step.getRoleCode())
                    : null;
            Long approverUserId = contact != null ? contact.getUserId() : 0L;
            String approverPhone = contact != null ? contact.getPhone() : null;

            UserNotificationVO noti = new UserNotificationVO();
            noti.setUserId(approverUserId);
            String title = "[결재요청] " + (targetType != null ? targetType : "") + " 결재 " + stepNo + "단계";
            String content = "결재 요청 ID " + requestId + " 에 대한 " + stepNo + "단계 결재가 필요합니다.";
            noti.setTitle(title);
            noti.setContent(content);
            noti.setRefType(NotificationRefType.APPROVAL.name());
            noti.setRefId(requestId);
            noti.setIsRead(false);
            notificationMapper.insert(noti);

            // SMS 외부채널 — phone 미확보 시 skip (mock이라도 일관성 유지)
            if (approverPhone != null && !approverPhone.isBlank()) {
                sendExternalMessage(MessageChannel.SMS, approverPhone, title, content, requestId);
            } else {
                log.debug("[MSG] 결재 단계 알림 phone 미확보 → SMS skip: stepRoleCode={}, requestId={}",
                        step.getRoleCode(), requestId);
            }
        } catch (Exception e) {
            log.warn("결재 알림 발송 실패 (non-critical): requestId={}", requestId, e);
        }
    }

    /**
     * 출금 결재 최종 승인 → 펌뱅킹 어댑터 요청.
     * <p>
     * 어댑터가 실패 응답을 줘도 결재 자체는 이미 APPROVED 이므로 예외를 던지지 않는다.
     * (재시도 배치는 transfer_status='FAILED' 필터로 별도 구현 예정.)
     */
    private void requestBankTransfer(Long withdrawId) {
        try {
            WithdrawRequestVO vo = withdrawMapper.selectVOById(withdrawId);
            if (vo == null) {
                log.warn("[BANK] withdraw 조회 실패: requestId={}", withdrawId);
                return;
            }
            BankTransferRequest req = BankTransferRequest.builder()
                    .withdrawRequestId(withdrawId)
                    .bankCode(vo.getBankCode())
                    .accountNo(vo.getAccountNo())          // 복호화 평문
                    .amount(vo.getAmount())
                    .memo("GA수수료 출금 #" + withdrawId)
                    .build();
            BankTransferResponse resp = bankTransferAdapter.requestTransfer(req);

            boolean ok = resp != null && resp.getStatus() != BankTransferStatus.FAILED;
            String nextStatus = ok ? "SENT" : "APPROVED";
            String failReason = ok ? null : (resp != null ? resp.getResultMessage() : "no response");
            withdrawMapper.updateTransferResult(
                    withdrawId,
                    resp != null ? resp.getTxId() : null,
                    resp != null && resp.getStatus() != null ? resp.getStatus().name() : null,
                    nextStatus,
                    failReason
            );
        } catch (Exception e) {
            // 어댑터 자체가 예외를 던진 경우 — APPROVED 상태로 두고 fail_reason 만 갱신.
            log.error("[BANK] 펌뱅킹 어댑터 호출 실패: requestId={}", withdrawId, e);
            try {
                withdrawMapper.updateTransferResult(
                        withdrawId, null, BankTransferStatus.FAILED.name(),
                        "APPROVED", e.getMessage());
            } catch (Exception ignore) { /* 결재 흐름 깨지지 않게 swallow */ }
        }
    }

    /** SMS/카카오 어댑터로 메시지 전송. phone 은 호출자가 user 마스터에서 조회해 전달. */
    private void sendExternalMessage(MessageChannel channel, String phone,
                                     String title, String content, Long refId) {
        try {
            MessageRequest req = MessageRequest.builder()
                    .channel(channel)
                    .phoneNumber(phone)
                    .title(title)
                    .content(content)
                    .refType(NotificationRefType.APPROVAL.name())
                    .refId(refId)
                    .build();
            messageAdapter.send(req);
        } catch (Exception e) {
            log.warn("[MSG] {} 발송 실패 (non-critical): refId={}", channel, refId, e);
        }
    }
}
