package com.ga.api.service.mobile;

import com.ga.api.service.statement.CommissionStatementService;
import com.ga.api.service.withdraw.WithdrawRequestService;
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.notice.UserNotificationMapper;
import com.ga.core.mapper.settle.CommissionStatementMapper;
import com.ga.core.mapper.settle.SettleMasterMapper;
import com.ga.core.mapper.user.UserMapper;
import com.ga.core.mapper.withdraw.WithdrawRequestMapper;
import com.ga.core.vo.notice.UserNotificationResp;
import com.ga.core.vo.notice.UserNotificationSearchParam;
import com.ga.core.vo.settle.CommissionStatementResp;
import com.ga.core.vo.settle.CommissionStatementSearchParam;
import com.ga.core.vo.settle.SettleMasterResp;
import com.ga.core.vo.settle.SettleSearchParam;
import com.ga.core.vo.user.UserVO;
import com.ga.core.vo.withdraw.WithdrawRequestResp;
import com.ga.core.vo.withdraw.WithdrawRequestSaveReq;
import com.ga.core.vo.withdraw.WithdrawRequestSearchParam;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;

/**
 * 모바일 PWA 전용 — 본인 데이터만 조회. agentId/userId 는 토큰에서 강제 추출.
 *
 * <p>클라이언트가 보낸 search param 의 agentId/userId 는 무시하고 SecurityContext 의 사용자로 덮어쓴다.
 * 본 서비스 외부에서는 본인 데이터 임의 조회가 절대 불가능하도록 보장.
 */
@Service
@RequiredArgsConstructor
public class MobileMeService {

    private final UserMapper userMapper;
    private final CommissionStatementMapper statementMapper;
    private final WithdrawRequestMapper withdrawMapper;
    private final UserNotificationMapper notificationMapper;
    private final SettleMasterMapper settleMasterMapper;
    private final WithdrawRequestService withdrawRequestService;
    private final CommissionStatementService statementService;
    private final com.ga.core.mapper.notice.UserDeviceTokenMapper deviceTokenMapper;

    /** 푸시용 디바이스 토큰 등록(UPSERT). 토큰은 본인(userId) 귀속. */
    @Transactional
    public void registerDeviceToken(String token, String platform) {
        if (token == null || token.isBlank()) throw new BizException(ErrorCode.INVALID_PARAMETER, "token");
        com.ga.core.vo.notice.UserDeviceTokenVO vo = new com.ga.core.vo.notice.UserDeviceTokenVO();
        vo.setUserId(currentUserId());
        vo.setToken(token);
        vo.setPlatform(platform == null || platform.isBlank() ? "WEB" : platform);
        deviceTokenMapper.upsert(vo);
    }

    /** 디바이스 토큰 해제(로그아웃/만료). 본인 토큰만. */
    @Transactional
    public void unregisterDeviceToken(String token) {
        if (token == null || token.isBlank()) throw new BizException(ErrorCode.INVALID_PARAMETER, "token");
        deviceTokenMapper.deactivate(token, currentUserId());
    }

    @Transactional(readOnly = true)
    public MeSummary summary() {
        Long userId = currentUserId();
        Long agentId = resolveAgentId(userId);

        long stTotal = 0L;
        long wdTotal = 0L;
        if (agentId != null) {
            CommissionStatementSearchParam sp = new CommissionStatementSearchParam();
            sp.setAgentId(agentId);
            sp.setPageNum(1);
            sp.setPageSize(1);
            sp.startPage();
            stTotal = PageResponse.of(statementMapper.selectList(sp)).getTotal();

            WithdrawRequestSearchParam wp = new WithdrawRequestSearchParam();
            wp.setAgentId(agentId);
            wp.setPageNum(1);
            wp.setPageSize(1);
            wp.startPage();
            wdTotal = PageResponse.of(withdrawMapper.selectList(wp)).getTotal();
        }
        int unreadCount = notificationMapper.selectUnreadCount(userId);

        return MeSummary.builder()
                .userId(userId)
                .agentId(agentId)
                .statementCount(stTotal)
                .withdrawRequestCount(wdTotal)
                .unreadNotificationCount(unreadCount)
                .build();
    }

    @Transactional(readOnly = true)
    public PageResponse<CommissionStatementResp> statements(CommissionStatementSearchParam param) {
        Long agentId = requireAgentId();
        param.setAgentId(agentId);              // 강제 덮어쓰기
        param.startPage();
        return PageResponse.of(statementMapper.selectList(param));
    }

    @Transactional(readOnly = true)
    public PageResponse<WithdrawRequestResp> withdrawRequests(WithdrawRequestSearchParam param) {
        Long agentId = requireAgentId();
        param.setAgentId(agentId);              // 강제 덮어쓰기
        param.startPage();
        return PageResponse.of(withdrawMapper.selectList(param));
    }

    @Transactional(readOnly = true)
    public PageResponse<UserNotificationResp> notifications(UserNotificationSearchParam param) {
        Long userId = currentUserId();
        param.setUserId(userId);                // 강제 덮어쓰기
        param.startPage();
        return PageResponse.of(notificationMapper.selectList(param));
    }

    @Transactional
    public void markNotificationRead(Long notificationId) {
        Long userId = currentUserId();
        notificationMapper.markRead(notificationId, userId);
    }

    /** 본인 알림 전체 읽음 처리. */
    @Transactional
    public int markAllNotificationsRead() {
        Long userId = currentUserId();
        return notificationMapper.markAllRead(userId);
    }

    /** 본인 명세서 단건. statementId 가 본인 agentId 의 것이 아니면 FORBIDDEN. */
    @Transactional(readOnly = true)
    public CommissionStatementResp statementDetail(Long statementId) {
        Long agentId = requireAgentId();
        CommissionStatementResp resp = statementMapper.selectDetailById(statementId);
        if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
        if (!agentId.equals(resp.getAgentId())) throw new BizException(ErrorCode.FORBIDDEN);
        return resp;
    }

    /** 본인 명세서 엑셀 다운로드. 본인 소유 검증 후 기존 다운로드 흐름 재사용. */
    @Transactional
    public byte[] downloadStatement(Long statementId) {
        statementDetail(statementId);   // 권한 검증 (FORBIDDEN/NOT_FOUND 던짐)
        return statementService.download(statementId);
    }

    /** 본인 phone 자기 수정 — 관리자 거치지 않고 본인이 직접 등록·정정. */
    @Transactional
    public void updateMyPhone(String phone) {
        Long userId = currentUserId();
        String trimmed = phone == null || phone.isBlank() ? null : phone.trim();
        userMapper.updatePhone(userId, trimmed);
    }

    /** 본인 출금 가능 정산 마스터 목록. 출금 신청 폼의 settleMasterId 드롭다운 용. */
    @Transactional(readOnly = true)
    public List<SettleMasterResp> withdrawableSettleMasters() {
        Long agentId = requireAgentId();
        SettleSearchParam param = new SettleSearchParam();
        param.setAgentId(agentId);
        param.setPageNum(1);
        param.setPageSize(50);
        param.startPage();
        return settleMasterMapper.selectList(param);
    }

    /** 본인 출금 신청 취소. 본인 소유 검증 후 기존 WithdrawRequestService.cancel 호출. */
    @Transactional
    public void cancelMyWithdrawRequest(Long requestId) {
        Long agentId = requireAgentId();
        com.ga.core.vo.withdraw.WithdrawRequestResp existing = withdrawMapper.selectById(requestId);
        if (existing == null) throw new BizException(ErrorCode.NOT_FOUND);
        if (!agentId.equals(existing.getAgentId())) throw new BizException(ErrorCode.FORBIDDEN);
        withdrawRequestService.cancel(requestId);
    }

    /**
     * 본인 정산 마스터의 출금 가능 잔액 조회 — 출금 신청 폼 미리 표시용.
     * 본인 소유가 아니면 FORBIDDEN, 없으면 NOT_FOUND.
     */
    @Transactional(readOnly = true)
    public WithdrawableBalance withdrawableBalance(Long settleMasterId) {
        Long agentId = requireAgentId();
        var master = settleMasterMapper.selectById(settleMasterId);
        if (master == null) throw new BizException(ErrorCode.NOT_FOUND, "정산 마스터를 찾을 수 없습니다");
        if (!agentId.equals(master.getAgentId())) throw new BizException(ErrorCode.FORBIDDEN);

        BigDecimal netAmount = master.getNetAmount() != null ? master.getNetAmount() : BigDecimal.ZERO;
        BigDecimal used = withdrawMapper.sumActiveAmountByMaster(settleMasterId);
        if (used == null) used = BigDecimal.ZERO;
        return WithdrawableBalance.builder()
                .settleId(settleMasterId)
                .netAmount(netAmount)
                .usedAmount(used)
                .remainingAmount(netAmount.subtract(used))
                .build();
    }

    /**
     * 본인 출금 신청 등록. agentId / requestedBy 는 토큰에서 강제 결정.
     * 클라이언트가 보낸 값은 무시한다.
     */
    @Transactional
    public Long createWithdrawRequest(MobileWithdrawSaveReq req) {
        Long userId = currentUserId();

        // 본인 소유 검증 + 잔여 한도 = net_amount - 활성 출금합계 (조회 로직 재사용)
        WithdrawableBalance balance = withdrawableBalance(req.getSettleMasterId());
        if (req.getAmount().compareTo(balance.getRemainingAmount()) > 0) {
            throw new BizException(ErrorCode.INVALID_PARAMETER,
                    "출금 가능 잔액(" + balance.getRemainingAmount() + ")을 초과합니다");
        }
        Long agentId = requireAgentId();

        WithdrawRequestSaveReq inner = new WithdrawRequestSaveReq();
        inner.setSettleMasterId(req.getSettleMasterId());
        inner.setAgentId(agentId);            // 강제
        inner.setRequestedBy(userId);         // 강제
        inner.setAmount(req.getAmount());
        inner.setBankCode(req.getBankCode());
        inner.setAccountNo(req.getAccountNo());

        return withdrawRequestService.create(inner);
    }

    // ── private ───────────────────────────────────────────────────────────────

    private Long currentUserId() {
        Long uid = SecurityUtil.getCurrentUserId();
        if (uid == null) throw new BizException(ErrorCode.UNAUTHORIZED);
        return uid;
    }

    private Long resolveAgentId(Long userId) {
        UserVO user = userMapper.selectById(userId);
        return user != null ? user.getAgentId() : null;
    }

    /** 본인 agent 가 연결돼 있어야만 통과. 미연결이면 FORBIDDEN. */
    private Long requireAgentId() {
        Long uid = currentUserId();
        Long agentId = resolveAgentId(uid);
        if (agentId == null) throw new BizException(ErrorCode.FORBIDDEN);
        return agentId;
    }

    @Data
    @Builder
    public static class MeSummary {
        private Long userId;
        private Long agentId;
        private long statementCount;
        private long withdrawRequestCount;
        private int unreadNotificationCount;
    }

    /** 출금 가능 잔액 — 폼 미리 표시용. */
    @Data
    @Builder
    public static class WithdrawableBalance {
        private Long settleId;
        private BigDecimal netAmount;
        private BigDecimal usedAmount;
        private BigDecimal remainingAmount;
    }

    /** 모바일 출금 신청 — 토큰에서 agentId/requestedBy 강제. */
    @Data
    public static class MobileWithdrawSaveReq {
        @NotNull
        private Long settleMasterId;
        @NotNull
        @Positive
        private BigDecimal amount;
        @NotBlank
        private String bankCode;
        @NotBlank
        private String accountNo;
    }
}
