package com.red.circle.other.app.common.gift;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway;
import com.red.circle.other.domain.live.LiveHeartbeatCache;
import com.red.circle.other.infra.database.cache.service.other.LiveHeartbeatCacheService;
import com.red.circle.other.infra.database.mongo.entity.live.RoomProfile;
import com.red.circle.other.infra.database.mongo.service.activity.RoomContributionActivityCountService;
import com.red.circle.other.infra.database.mongo.service.live.RoomContributionCountService;
import com.red.circle.other.infra.database.mongo.service.live.RoomContributionRankCountService;
import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService;
import com.red.circle.other.infra.database.rds.service.live.RoomContributionBalanceService;
import java.math.BigDecimal;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

class LuckyGiftRewardSettlementServiceTest {

  private static final long ROOM_ID = 2001L;
  private static final long ROOM_OWNER_ID = 3001L;
  private static final long SEND_USER_ID = 4001L;
  private static final long ACCEPT_USER_ONE = 5001L;
  private static final long ACCEPT_USER_TWO = 5002L;

  private LiveHeartbeatCacheService liveHeartbeatCacheService;
  private RoomProfileManagerService roomProfileManagerService;
  private UserRegionGateway userRegionGateway;
  private RoomContributionRankCountService roomContributionRankCountService;
  private RoomContributionBalanceService roomContributionBalanceService;
  private RoomContributionCountService roomContributionCountService;
  private RoomContributionActivityCountService roomContributionActivityCountService;
  private LuckyGiftRewardSettlementService service;

  @BeforeEach
  void setUp() {
    liveHeartbeatCacheService = mock(LiveHeartbeatCacheService.class);
    roomProfileManagerService = mock(RoomProfileManagerService.class);
    userRegionGateway = mock(UserRegionGateway.class);
    roomContributionRankCountService = mock(RoomContributionRankCountService.class);
    roomContributionBalanceService = mock(RoomContributionBalanceService.class);
    roomContributionCountService = mock(RoomContributionCountService.class);
    roomContributionActivityCountService = mock(RoomContributionActivityCountService.class);
    service = new LuckyGiftRewardSettlementService(
        userRegionGateway,
        liveHeartbeatCacheService,
        roomProfileManagerService,
        roomContributionRankCountService,
        roomContributionBalanceService,
        roomContributionCountService,
        roomContributionActivityCountService
    );
  }

  @Test
  void settle_shouldApplyHeartbeatAndRoomContributionWithTenPercentReward() {
    when(liveHeartbeatCacheService.listLiveHeartbeat(ROOM_ID)).thenReturn(List.of(
        new LiveHeartbeatCache().setId(ACCEPT_USER_ONE).setHeartbeatVal(5L)
    ));

    RoomProfile roomProfile = new RoomProfile();
    roomProfile.setId(ROOM_ID);
    roomProfile.setUserId(ROOM_OWNER_ID);
    roomProfile.setSysOrigin("LIKEI");
    when(roomProfileManagerService.getProfileById(ROOM_ID)).thenReturn(roomProfile);
    when(userRegionGateway.getRegionCode(ROOM_OWNER_ID)).thenReturn("SA");
    when(userRegionGateway.getRegionCode(SEND_USER_ID)).thenReturn("SA");

    service.settle(buildEvent());

    ArgumentCaptor<List<LiveHeartbeatCache>> heartbeatCaptor = ArgumentCaptor.forClass(List.class);
    verify(liveHeartbeatCacheService).create(eq(ROOM_ID), heartbeatCaptor.capture());
    List<LiveHeartbeatCache> caches = heartbeatCaptor.getValue();
    assertEquals(2, caches.size());
    assertEquals(13L, findHeartbeat(caches, ACCEPT_USER_ONE));
    assertEquals(2L, findHeartbeat(caches, ACCEPT_USER_TWO));

    BigDecimal expectedContribution = BigDecimal.TEN;
    verify(roomContributionRankCountService).incr("LIKEI", ROOM_ID, SEND_USER_ID,
        expectedContribution);
    verify(roomContributionBalanceService).incrTotalQuantity(ROOM_ID, ROOM_OWNER_ID,
        expectedContribution);
    verify(roomContributionCountService).incrQuantity("LIKEI", ROOM_ID, ROOM_OWNER_ID,
        expectedContribution);
    verify(roomContributionActivityCountService).incrQuantity(ROOM_ID, expectedContribution);
  }

  @Test
  void settle_shouldSkipRoomContributionWhenSenderRegionDiffers() {
    when(liveHeartbeatCacheService.listLiveHeartbeat(ROOM_ID)).thenReturn(List.of());

    RoomProfile roomProfile = new RoomProfile();
    roomProfile.setId(ROOM_ID);
    roomProfile.setUserId(ROOM_OWNER_ID);
    roomProfile.setSysOrigin("LIKEI");
    when(roomProfileManagerService.getProfileById(ROOM_ID)).thenReturn(roomProfile);
    when(userRegionGateway.getRegionCode(ROOM_OWNER_ID)).thenReturn("SA");
    when(userRegionGateway.getRegionCode(SEND_USER_ID)).thenReturn("PK");

    service.settle(buildEvent());

    verify(liveHeartbeatCacheService).create(eq(ROOM_ID), any());
    verify(roomContributionRankCountService, never()).incr(any(), any(), any(), any());
    verify(roomContributionBalanceService, never()).incrTotalQuantity(any(), any(), any());
    verify(roomContributionCountService, never()).incrQuantity(any(), any(), any(), any());
    verify(roomContributionActivityCountService, never()).incrQuantity(any(), any());
  }

  private LuckyGiftRewardStreamEvent buildEvent() {
    return new LuckyGiftRewardStreamEvent()
        .setEventId("LUCKY_GIFT_REWARD:biz-1")
        .setBusinessId("biz-1")
        .setSysOrigin("LIKEI")
        .setRoomId(ROOM_ID)
        .setSendUserId(SEND_USER_ID)
        .setResults(List.of(
            new LuckyGiftRewardStreamEvent.Result()
                .setOrderSeed("order-1")
                .setAcceptUserId(ACCEPT_USER_ONE)
                .setRewardNum(80L),
            new LuckyGiftRewardStreamEvent.Result()
                .setOrderSeed("order-2")
                .setAcceptUserId(ACCEPT_USER_TWO)
                .setRewardNum(25L)
        ));
  }

  private long findHeartbeat(List<LiveHeartbeatCache> caches, long userId) {
    return caches.stream()
        .filter(cache -> cache != null && cache.getId() != null && cache.getId() == userId)
        .findFirst()
        .map(LiveHeartbeatCache::getHeartbeatVal)
        .orElse(0L);
  }
}
