package com.red.circle.other.app.command.signin;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.common.business.enums.SendPropsOrigin;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.framework.core.response.CommonErrorCode;
import com.red.circle.other.app.dto.clientobject.signin.CheckInResultCO;
import com.red.circle.other.app.dto.cmd.signin.SupplementCmd;
import com.red.circle.other.infra.common.props.PropsSendCommon;
import com.red.circle.other.infra.database.rds.dao.checkin.SignInDailyConfigDAO;
import com.red.circle.other.infra.database.rds.dao.checkin.UserSignInRecordDAO;
import com.red.circle.other.infra.database.rds.entity.SignInDailyConfig;
import com.red.circle.other.infra.database.rds.entity.UserSignInRecord;
import com.red.circle.other.infra.database.rds.entity.activity.PropsActivityRewardConfig;
import com.red.circle.other.infra.database.rds.service.activity.PropsActivityRewardConfigService;
import com.red.circle.other.infra.gateway.lottery.LotteryBackpackGateway;
import com.red.circle.other.inner.asserts.OtherErrorCode;
import com.red.circle.other.inner.endpoint.activity.LotteryTicketClient;
import com.red.circle.other.inner.model.cmd.material.PrizeDescribe;
import com.red.circle.other.inner.model.cmd.material.PrizeDescribeRewardCmd;
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
import com.red.circle.tool.core.tuple.PennyAmount;
import com.red.circle.wallet.inner.endpoint.wallet.WalletGoldClient;
import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd;
import com.red.circle.wallet.inner.model.enums.GoldOrigin;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.Collections;
import java.util.List;

@Slf4j
@Component
@RequiredArgsConstructor
public class SupplementCheckInExe {

    private final SignInDailyConfigDAO dailyConfigDAO;
    private final UserSignInRecordDAO recordDAO;
    private final WalletGoldClient walletGoldClient;
    private final PropsSendCommon propsSendCommon;
    private final PropsActivityRewardConfigService propsActivityRewardConfigService;
    private final LotteryTicketClient lotteryTicketClient;
    private final LotteryBackpackGateway lotteryBackpackGateway;

    private final static Long FIXED_ACTIVITY_ID = 2004471533988167125L;

    private static final int BASE_SUPPLEMENT_COST = 500;

    @Transactional(rollbackFor = Exception.class)
    public CheckInResultCO execute(SupplementCmd cmd) {
        Long userId = cmd.getReqUserId();
        Integer targetDayIndex = cmd.getDayIndex();

        LocalDate saudiToday = ZonedDateTimeAsiaRiyadhUtils.now().toLocalDate();
        LocalDate weekStartDate = saudiToday.with(DayOfWeek.MONDAY);
        int currentDayIndex = saudiToday.getDayOfWeek().getValue();

        ResponseAssert.isTrue(OtherErrorCode.SIGN_IN_SUPPLEMENT_ONLY_PAST,
                targetDayIndex < currentDayIndex);

        long alreadySigned = recordDAO.selectCount(
                new LambdaQueryWrapper<UserSignInRecord>()
                        .eq(UserSignInRecord::getUserId, userId)
                        .eq(UserSignInRecord::getWeekStartDate, weekStartDate)
                        .eq(UserSignInRecord::getDayIndex, targetDayIndex)
        );
        ResponseAssert.isFalse(OtherErrorCode.SIGN_IN_DATE_ALREADY_CHECKED, alreadySigned > 0);

        for (int i = 1; i < targetDayIndex; i++) {
            long count = recordDAO.selectCount(
                    new LambdaQueryWrapper<UserSignInRecord>()
                            .eq(UserSignInRecord::getUserId, userId)
                            .eq(UserSignInRecord::getWeekStartDate, weekStartDate)
                            .eq(UserSignInRecord::getDayIndex, i)
            );
            ResponseAssert.isTrue(OtherErrorCode.SIGN_IN_NEED_PREVIOUS, count > 0);
        }

        List<SignInDailyConfig> configList = dailyConfigDAO.selectList(
                new LambdaQueryWrapper<SignInDailyConfig>()
                        .eq(SignInDailyConfig::getDayIndex, targetDayIndex)
        );
        ResponseAssert.isTrue(OtherErrorCode.SIGN_IN_CONFIG_NOT_FOUND, !configList.isEmpty());

        SignInDailyConfig config = configList.get(0);

        int supplementCost = BASE_SUPPLEMENT_COST;
        if (config.getIsPaid() == 1) {
            supplementCost = config.getPaidCost() + BASE_SUPPLEMENT_COST;
        }

        decreaseUserGold(userId, cmd.getReqSysOrigin().getOrigin(), supplementCost, weekStartDate + "_supplement_" + targetDayIndex);

        UserSignInRecord record = new UserSignInRecord();
        record.setUserId(userId);
        record.setWeekStartDate(weekStartDate);
        record.setSignDate(saudiToday);
        record.setDayIndex(targetDayIndex);
        record.setIsPaid(config.getIsPaid());
        record.setPaidCost(config.getIsPaid() == 1 ? config.getPaidCost() : 0);
        record.setIsSupplement(1);
        record.setSupplementCost(supplementCost);
        record.setIsRewarded(1);
        record.setResourceGroupId(config.getResourceGroupId());
        recordDAO.insert(record);

        sendReward(userId, config);

        // 发放抽奖券
        sendLotteryTickets(userId, config, record.getId());

        String bizNo = "supplement:" + saudiToday + "_" + targetDayIndex + ":" + userId;
        lotteryBackpackGateway.recordTicketAdd(userId, config.getLotteryTicketCount(), "sign_in", bizNo);

        CheckInResultCO resultCO = new CheckInResultCO();
        resultCO.setSuccess(true);
        resultCO.setMessage("Make-up successful");
        resultCO.setPaidCost(config.getIsPaid() == 1 ? config.getPaidCost() : 0);
        resultCO.setSupplementCost(supplementCost);

        return resultCO;
    }

    private void decreaseUserGold(Long userId, String sysOrigin, Integer amount, String eventId) {
        GoldReceiptCmd goldCmd = GoldReceiptCmd.builder()
                .appExpenditure()
                .userId(userId)
                .amount(PennyAmount.ofDollar(amount.longValue()))
                .eventId(eventId)
                .sysOrigin(sysOrigin)
                .origin(GoldOrigin.SIGN_IN_SUPPLEMENT)
                .build();

        try {
            walletGoldClient.changeBalance(goldCmd);
        } catch (Exception e) {
            log.error("Decrease gold failed for supplement userId={} amount={} eventId={}", 
                    userId, amount, eventId, e);
            throw new RuntimeException("Failed to decrease gold for supplement");
        }
    }

    private void sendReward(Long userId, SignInDailyConfig signInDailyConfig) {
        Long resourceGroupId = signInDailyConfig.getResourceGroupId();
        List<PropsActivityRewardConfig> configs = propsActivityRewardConfigService.listByGroupId(resourceGroupId);
        ResponseAssert.notEmpty(CommonErrorCode.NOT_FOUND_MAPPING_INFO, configs);

        configs.forEach(config -> {
            propsSendCommon.send(new PrizeDescribeRewardCmd()
                    .setTrackId(config.getGroupId())
                    .setAcceptUserId(userId)
                    .setOrigin(SendPropsOrigin.SIGN_IN_REWARD)
                    .setSysOrigin(SysOriginPlatformEnum.LIKEI)
                    .setPrizes(Collections.singletonList(new PrizeDescribe()
                            .setTrackId(config.getGroupId())
                            .setType(config.getType())
                            .setDetailType(config.getDetailType())
                            .setContent(config.getContent())
                            .setQuantity(config.getQuantity())))
            );
        });
    }


    /**
     * 发放抽奖券
     */
    private void sendLotteryTickets(Long userId, SignInDailyConfig config, Long trackId) {
        if (config.getLotteryTicketCount() == null || config.getLotteryTicketCount() <= 0) {
            return;
        }

        try {
            lotteryTicketClient.addTicket(
                    userId,
                    FIXED_ACTIVITY_ID,
                    config.getLotteryTicketCount(),
                    "SIGN_IN",
                    "SIGN_IN_DAY_" + config.getDayIndex()
            );
        } catch (Exception e) {
            log.error("发放签到抽奖券失败, userId={}, dayIndex={}, count={}",
                    userId, config.getDayIndex(), config.getLotteryTicketCount(), e);
            throw new RuntimeException("Failed to send lottery tickets");
        }
    }

}
