package com.red.circle.other.infra.common.activity;

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.framework.core.response.ResponseErrorCode;
import com.red.circle.framework.mybatis.constant.PageConstant;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.infra.database.rds.entity.badge.BadgeBackpack;
import com.red.circle.other.infra.database.rds.entity.props.PropsBackpack;
import com.red.circle.other.infra.database.rds.entity.props.RunningWaterUserProps;
import com.red.circle.other.infra.database.rds.entity.vip.UserVipState;
import com.red.circle.other.infra.database.rds.entity.vip.VipLevelConfig;
import com.red.circle.other.infra.database.rds.entity.vip.VipOrderRecord;
import com.red.circle.other.infra.database.rds.service.badge.BadgeBackpackService;
import com.red.circle.other.infra.database.rds.service.props.PropsBackpackService;
import com.red.circle.other.infra.database.rds.service.props.RunningWaterUserPropsService;
import com.red.circle.other.infra.database.rds.service.vip.UserVipStateService;
import com.red.circle.other.infra.database.rds.service.vip.VipLevelConfigService;
import com.red.circle.other.infra.database.rds.service.vip.VipOrderRecordService;
import com.red.circle.other.inner.enums.material.BadgeBackpackExpireType;
import com.red.circle.other.inner.enums.material.PropsCommodityType;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.parse.DataTypeUtils;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
 * Sends operator-managed VIP rewards from resource groups.
 */
@Component
@RequiredArgsConstructor
public class VipActivityRewardCommon {

  public static final String VIP_DETAIL_TYPE = "NOBLE_VIP";

  private static final int DEFAULT_DURATION_DAYS = 30;
  private static final String STATUS_ACTIVE = "ACTIVE";
  private static final String STATUS_SUCCESS = "SUCCESS";
  private static final String ORDER_TYPE_RESOURCE_GROUP = "RESOURCE_GROUP";
  private static final String EVENT_PREFIX = "RESOURCE-GROUP-VIP-";
  private static final String WATER_ORIGIN = "VIP_PURCHASE";
  private static final String WATER_ORIGIN_DESC = "VIP purchase";
  private static final Timestamp MYSQL_DATETIME_MAX = Timestamp.valueOf("2038-01-19 00:00:00");

  private final UserProfileGateway userProfileGateway;
  private final BadgeBackpackService badgeBackpackService;
  private final PropsBackpackService propsBackpackService;
  private final UserVipStateService userVipStateService;
  private final VipLevelConfigService vipLevelConfigService;
  private final VipOrderRecordService vipOrderRecordService;
  private final RunningWaterUserPropsService runningWaterUserPropsService;

  public boolean isVipDetail(String type, String detailType) {
    return Objects.equals("PROPS", type) && Objects.equals(VIP_DETAIL_TYPE, detailType);
  }

  public boolean existsEnabledConfig(SysOriginPlatformEnum sysOrigin, String content) {
    Long configId = DataTypeUtils.toLong(content);
    return Objects.nonNull(vipLevelConfigService.getEnabledById(sysOrigin.name(), configId));
  }

  @Transactional(rollbackFor = Exception.class)
  public void gift(SysOriginPlatformEnum sysOrigin, Long userId, Long trackId, String content,
      SendPropsOrigin origin) {
    Long configId = DataTypeUtils.toLong(content);
    VipLevelConfig config = vipLevelConfigService.getEnabledById(sysOrigin.name(), configId);
    ResponseAssert.notNull(CommonErrorCode.NOT_FOUND_MAPPING_INFO, config);

    int durationDays = durationDays(config);
    Timestamp now = TimestampUtils.now();
    UserVipState currentState = getUserVipState(sysOrigin.name(), userId);
    boolean currentActive = isActive(currentState, now);
    int fromLevel = currentActive ? Objects.requireNonNullElse(currentState.getLevel(), 0) : 0;

    ResponseAssert.isFalse(ResponseErrorCode.REQUEST_PARAMETER_ERROR,
        currentActive && fromLevel > Objects.requireNonNullElse(config.getLevel(), 0));

    Timestamp startAt = currentActive && Objects.nonNull(currentState.getStartAt())
        ? currentState.getStartAt()
        : now;
    Timestamp expireAt = capExpireTime(resolveExpireAt(currentState, config, currentActive, now));
    Long orderId = IdWorkerUtils.getId();
    String eventId = EVENT_PREFIX + orderId;

    vipOrderRecordService.save(buildOrder(orderId, eventId, sysOrigin.name(), userId, fromLevel,
        config, startAt, expireAt, now));
    upsertUserVipState(currentState, sysOrigin.name(), userId, config, startAt, expireAt, now);
    giftVipResources(userId, trackId, config, expireAt, durationDays, now, origin);
    userProfileGateway.removeCacheAll(userId);
  }

  private VipOrderRecord buildOrder(Long orderId, String eventId, String sysOrigin, Long userId,
      Integer fromLevel, VipLevelConfig config, Timestamp startAt, Timestamp expireAt,
      Timestamp now) {
    VipOrderRecord order = new VipOrderRecord()
        .setId(orderId)
        .setEventId(eventId)
        .setSysOrigin(sysOrigin)
        .setUserId(userId)
        .setOrderType(ORDER_TYPE_RESOURCE_GROUP)
        .setFromLevel(fromLevel)
        .setToLevel(config.getLevel())
        .setDurationDays(durationDays(config))
        .setOriginalPriceGold(Objects.requireNonNullElse(config.getPriceGold(), 0L))
        .setPaidGold(0L)
        .setStatus(STATUS_SUCCESS)
        .setStartAt(startAt)
        .setExpireAt(expireAt)
        .setErrorMessage("");
    order.setCreateTime(now);
    order.setUpdateTime(now);
    return order;
  }

  private void upsertUserVipState(UserVipState currentState, String sysOrigin, Long userId,
      VipLevelConfig config, Timestamp startAt, Timestamp expireAt, Timestamp now) {
    UserVipState state = Objects.nonNull(currentState)
        ? currentState
        : new UserVipState().setId(IdWorkerUtils.getId());
    if (Objects.isNull(currentState)) {
      state.setCreateTime(now);
    }
    state.setSysOrigin(sysOrigin);
    state.setUserId(userId);
    state.setLevel(config.getLevel());
    state.setLevelCode(levelCode(config));
    state.setDisplayName(displayName(config));
    state.setStatus(STATUS_ACTIVE);
    state.setStartAt(startAt);
    state.setExpireAt(expireAt);
    copyResources(state, config);
    state.setUpdateTime(now);
    if (Objects.nonNull(currentState)) {
      userVipStateService.updateSelectiveById(state);
      return;
    }
    userVipStateService.save(state);
  }

  private void copyResources(UserVipState state, VipLevelConfig config) {
    state.setBadgeResourceId(config.getBadgeResourceId());
    state.setBadgeName(config.getBadgeName());
    state.setBadgeUrl(config.getBadgeUrl());
    state.setShortBadgeResourceId(config.getShortBadgeResourceId());
    state.setShortBadgeName(config.getShortBadgeName());
    state.setShortBadgeUrl(config.getShortBadgeUrl());
    state.setAvatarFrameResourceId(config.getAvatarFrameResourceId());
    state.setAvatarFrameName(config.getAvatarFrameName());
    state.setAvatarFrameUrl(config.getAvatarFrameUrl());
    state.setEntryEffectResourceId(config.getEntryEffectResourceId());
    state.setEntryEffectName(config.getEntryEffectName());
    state.setEntryEffectUrl(config.getEntryEffectUrl());
    state.setChatBubbleResourceId(config.getChatBubbleResourceId());
    state.setChatBubbleName(config.getChatBubbleName());
    state.setChatBubbleUrl(config.getChatBubbleUrl());
    state.setFloatPictureResourceId(config.getFloatPictureResourceId());
    state.setFloatPictureName(config.getFloatPictureName());
    state.setFloatPictureUrl(config.getFloatPictureUrl());
    state.setBackgroundCardResourceId(config.getBackgroundCardResourceId());
    state.setBackgroundCardName(config.getBackgroundCardName());
    state.setBackgroundCardUrl(config.getBackgroundCardUrl());
    state.setEffectImageResourceId(config.getEffectImageResourceId());
    state.setEffectImageName(config.getEffectImageName());
    state.setEffectImageUrl(config.getEffectImageUrl());
  }

  private void giftVipResources(Long userId, Long trackId, VipLevelConfig config,
      Timestamp expireAt, Integer days, Timestamp now, SendPropsOrigin origin) {
    for (VipResourceGrant resource : vipResourcesFromConfig(config)) {
      if (resource.badge) {
        giveBadge(userId, resource.resourceId, expireAt, days);
        continue;
      }
      giveProp(userId, trackId, resource.resourceId, resource.type, expireAt, days, now, origin);
    }
  }

  private List<VipResourceGrant> vipResourcesFromConfig(VipLevelConfig config) {
    return List.of(
        VipResourceGrant.badge(config.getBadgeResourceId()),
        VipResourceGrant.badge(config.getShortBadgeResourceId()),
        VipResourceGrant.prop(config.getAvatarFrameResourceId(), PropsCommodityType.AVATAR_FRAME),
        VipResourceGrant.prop(config.getEntryEffectResourceId(), PropsCommodityType.RIDE),
        VipResourceGrant.prop(config.getChatBubbleResourceId(), PropsCommodityType.CHAT_BUBBLE),
        VipResourceGrant.prop(config.getFloatPictureResourceId(), PropsCommodityType.FLOAT_PICTURE),
        VipResourceGrant.prop(config.getBackgroundCardResourceId(), PropsCommodityType.DATA_CARD)
    ).stream().filter(VipResourceGrant::valid).toList();
  }

  private void giveBadge(Long userId, Long badgeId, Timestamp expireAt, Integer days) {
    badgeBackpackService.activationTemporaryAndUse(userId, badgeId, days);
    BadgeBackpack backpack = badgeBackpackService.getByUserIdByBadgeId(userId, badgeId);
    if (Objects.isNull(backpack)) {
      return;
    }
    backpack.setUseProps(Boolean.TRUE);
    backpack.setExpireType(BadgeBackpackExpireType.TEMPORARY.name());
    backpack.setExpireTime(expireAt);
    badgeBackpackService.updateSelectiveById(backpack);
  }

  private void giveProp(Long userId, Long trackId, Long propsId, PropsCommodityType type,
      Timestamp expireAt, Integer days, Timestamp now, SendPropsOrigin origin) {
    PropsBackpack oldBackpack = propsBackpackService.getUserProps(userId, propsId, type);
    boolean useProps = resolveUseProps(userId, propsId, type, oldBackpack, now);
    propsBackpackService.addPropsBackpack(new PropsBackpack()
        .setUserId(userId)
        .setPropsId(propsId)
        .setType(type.name())
        .setExpireTime(expireAt)
        .setUseProps(useProps)
        .setAllowGive(Boolean.FALSE));

    RunningWaterUserProps water = new RunningWaterUserProps()
        .setUserId(userId)
        .setPropsId(propsId)
        .setPropsCandy(BigDecimal.ZERO)
        .setType(type.name())
        .setOrigin(Objects.nonNull(origin) ? origin.name() : WATER_ORIGIN)
        .setOriginDesc(Objects.nonNull(origin) ? origin.getDesc() : WATER_ORIGIN_DESC)
        .setDays(days);
    water.setCreateTime(now);
    water.setUpdateTime(now);
    water.setCreateUser(Objects.requireNonNullElse(trackId, 0L));
    water.setUpdateUser(0L);
    runningWaterUserPropsService.save(water);
  }

  private boolean resolveUseProps(Long userId, Long propsId, PropsCommodityType type,
      PropsBackpack oldBackpack, Timestamp now) {
    if (Objects.nonNull(oldBackpack)) {
      return Boolean.TRUE.equals(oldBackpack.getUseProps());
    }
    List<PropsBackpack> notExpiredSameType = propsBackpackService.query()
        .eq(PropsBackpack::getUserId, userId)
        .eq(PropsBackpack::getType, type.name())
        .gt(PropsBackpack::getExpireTime, now)
        .list();
    return notExpiredSameType.stream()
        .noneMatch(item -> Boolean.TRUE.equals(item.getUseProps())
            && !Objects.equals(item.getPropsId(), propsId));
  }

  private UserVipState getUserVipState(String sysOrigin, Long userId) {
    return userVipStateService.query()
        .eq(UserVipState::getSysOrigin, sysOrigin)
        .eq(UserVipState::getUserId, userId)
        .last(PageConstant.LIMIT_ONE)
        .getOne();
  }

  private boolean isActive(UserVipState state, Timestamp now) {
    return Objects.nonNull(state)
        && STATUS_ACTIVE.equals(state.getStatus())
        && Objects.nonNull(state.getExpireAt())
        && state.getExpireAt().after(now)
        && Objects.nonNull(state.getLevel())
        && state.getLevel() > 0;
  }

  private Timestamp resolveExpireAt(UserVipState currentState, VipLevelConfig config,
      boolean currentActive, Timestamp now) {
    Timestamp plusDuration = plusDays(now, durationDays(config));
    if (!currentActive) {
      return plusDuration;
    }
    if (Objects.equals(currentState.getLevel(), config.getLevel())) {
      return plusDays(max(currentState.getExpireAt(), now), durationDays(config));
    }
    return max(currentState.getExpireAt(), plusDuration);
  }

  private int durationDays(VipLevelConfig config) {
    Integer days = config.getDurationDays();
    return Objects.nonNull(days) && days > 0 ? days : DEFAULT_DURATION_DAYS;
  }

  private String levelCode(VipLevelConfig config) {
    if (Objects.nonNull(config.getLevelCode()) && !config.getLevelCode().isBlank()) {
      return config.getLevelCode();
    }
    return "VIP" + Objects.requireNonNullElse(config.getLevel(), 0);
  }

  private String displayName(VipLevelConfig config) {
    if (Objects.nonNull(config.getDisplayName()) && !config.getDisplayName().isBlank()) {
      return config.getDisplayName();
    }
    return "VIP " + Objects.requireNonNullElse(config.getLevel(), 0);
  }

  private Timestamp max(Timestamp first, Timestamp second) {
    if (Objects.isNull(first)) {
      return second;
    }
    if (Objects.isNull(second)) {
      return first;
    }
    return first.after(second) ? first : second;
  }

  private Timestamp plusDays(Timestamp value, int days) {
    return Timestamp.from(value.toInstant().plus(Duration.ofDays(days)));
  }

  private Timestamp capExpireTime(Timestamp expireTime) {
    if (Objects.isNull(expireTime)) {
      return null;
    }
    return expireTime.after(MYSQL_DATETIME_MAX) ? MYSQL_DATETIME_MAX : expireTime;
  }

  private static final class VipResourceGrant {

    private final Long resourceId;
    private final PropsCommodityType type;
    private final boolean badge;

    private VipResourceGrant(Long resourceId, PropsCommodityType type, boolean badge) {
      this.resourceId = resourceId;
      this.type = type;
      this.badge = badge;
    }

    private static VipResourceGrant badge(Long resourceId) {
      return new VipResourceGrant(resourceId, null, true);
    }

    private static VipResourceGrant prop(Long resourceId, PropsCommodityType type) {
      return new VipResourceGrant(resourceId, type, false);
    }

    private boolean valid() {
      return Objects.nonNull(resourceId) && resourceId > 0;
    }
  }
}
