package com.red.circle.console.app.service.app.order;

import com.alibaba.excel.EasyExcel;
import com.red.circle.console.app.dto.clienobject.order.InAppPurchaseDetailsCO;
import com.red.circle.console.app.excel.EasyExcelUtils;
import com.red.circle.console.app.excel.model.order.InAppPurchaseDetailsExportModel;
import com.red.circle.console.infra.database.rds.entity.admin.User;
import com.red.circle.console.infra.database.rds.service.admin.UserService;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.order.inner.asserts.OrderErrorCode;
import com.red.circle.order.inner.endpoint.InAppPurchaseDetailsClient;
import com.red.circle.order.inner.model.cmd.inapp.InAppPurchaseDetailsQryCmd;
import com.red.circle.order.inner.model.dto.inapp.InAppPurchaseFactoryDTO;
import com.red.circle.order.inner.model.dto.inapp.InAppPurchaseDetailsDTO;
import com.red.circle.tool.core.collection.CollectionUtils;
import com.red.circle.tool.core.text.StringUtils;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import com.red.circle.other.inner.endpoint.user.user.UserProfileClient;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * 内购详情 实现.
 *
 * @author pengliang on 2022/10/31
 */
@Service
@RequiredArgsConstructor
public class InAppPurchaseBackServiceImpl implements InAppPurchaseBackService {

  private final UserService userService;
  private final UserProfileClient userProfileClient;
  private final InAppPurchaseDetailsClient inAppPurchaseDetailsClient;

  @Override
  public InAppPurchaseDetailsCO getDetails(String id) {
    InAppPurchaseDetailsDTO details = ResponseAssert.requiredSuccess(
        inAppPurchaseDetailsClient.getById(id)
    );

    ResponseAssert.notNull(OrderErrorCode.NO_PURCHASE_RECORD_FOUND, details);

    InAppPurchaseDetailsCO co = new InAppPurchaseDetailsCO()
        .setDetails(details);

    if (details.receiptTypeEqReceipt()) {
      co.setAcceptUserProfile(
          ResponseAssert.requiredSuccess(userProfileClient.getByUserId(details.getAcceptUserId())));
      co.setCreateNickname(Optional.ofNullable(userService.getById(details.getCreateUser()))
          .map(User::getNickname)
          .orElse(""));
    }

    if (details.receiptTypeEqPayment()) {
      if (Objects.equals(details.getCreateUser(), details.getAcceptUserId())) {
        co.setAcceptUserProfile(
            ResponseAssert.requiredSuccess(
                userProfileClient.getByUserId(details.getAcceptUserId())));
      } else {
        Map<Long, UserProfileDTO> userProfileMap = ResponseAssert.requiredSuccess(
            userProfileClient.mapByUserIds(
                Set.of(details.getCreateUser(), details.getAcceptUserId()))
        );
        co.setAcceptUserProfile(userProfileMap.get(details.getAcceptUserId()));
        co.setCreateUserProfile(userProfileMap.get(details.getCreateUser()));
      }
    }

    return co;
  }

  @Override
  public List<InAppPurchaseDetailsCO> listDetails(InAppPurchaseDetailsQryCmd query) {
    List<InAppPurchaseDetailsDTO> inAppPurchaseDetails = ResponseAssert.requiredSuccess(
        inAppPurchaseDetailsClient.list(query)
    );

    if (CollectionUtils.isEmpty(inAppPurchaseDetails)) {
      return CollectionUtils.newArrayList();
    }

    Set<Long> backUserIds = inAppPurchaseDetails.stream().filter(
            receipt -> receipt.receiptTypeEqReceipt() && Objects.nonNull(receipt.getCreateUser()))
        .map(InAppPurchaseDetailsDTO::getCreateUser)
        .collect(Collectors.toSet());

    Set<Long> appUserIds = Stream.of(inAppPurchaseDetails.stream().filter(
                    receipt -> receipt.receiptTypeEqPayment() && Objects.nonNull(receipt.getCreateUser()))
                .map(InAppPurchaseDetailsDTO::getCreateUser)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet()),
            inAppPurchaseDetails.stream().map(InAppPurchaseDetailsDTO::getAcceptUserId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet()))
        .flatMap(Collection::stream)
        .collect(Collectors.toSet());

    Map<Long, User> userMap = userService.mapBackUser(backUserIds);
    Map<Long, UserProfileDTO> userProfileMap = ResponseAssert.requiredSuccess(
        userProfileClient.mapByUserIds(appUserIds)
    );

    return inAppPurchaseDetails.stream().map(details -> {
      InAppPurchaseDetailsCO co = new InAppPurchaseDetailsCO()
          .setDetails(details);

      if (details.receiptTypeEqReceipt()) {
        co.setAcceptUserProfile((userProfileMap.get(details.getAcceptUserId())));
        co.setCreateNickname(Optional.ofNullable(userMap.get(details.getCreateUser()))
            .map(User::getNickname)
            .orElse(""));
      }

      if (details.receiptTypeEqPayment()) {
        if (Objects.equals(details.getCreateUser(), details.getAcceptUserId())) {
          co.setAcceptUserProfile(userProfileMap.get(details.getAcceptUserId()));
        } else {
          co.setAcceptUserProfile(userProfileMap.get(details.getAcceptUserId()));
          co.setCreateUserProfile(userProfileMap.get(details.getCreateUser()));
        }
      }
      return co;
    }).collect(Collectors.toList());
  }

  @Override
  public Map<String, Object> statistics(InAppPurchaseDetailsQryCmd query) {
    return ResponseAssert.requiredSuccess(
        inAppPurchaseDetailsClient.statistics(query)
    );
  }

  @Override
  public void exportDetails(HttpServletResponse response, InAppPurchaseDetailsQryCmd query)
      throws IOException {
    query.setLastId(null);
    query.setLimit(null);
    query.setExportAll(Boolean.TRUE);

    List<InAppPurchaseDetailsExportModel> exportRows = listDetails(query).stream()
        .map(this::toExportModel)
        .collect(Collectors.toList());
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setCharacterEncoding(StandardCharsets.UTF_8.name());
    String fileName = URLEncoder.encode("InAppPurchaseOrders", StandardCharsets.UTF_8)
        .replaceAll("\\+", "%20");
    response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    EasyExcel.write(response.getOutputStream(), InAppPurchaseDetailsExportModel.class)
        .registerWriteHandler(EasyExcelUtils.getStyleStrategy())
        .sheet("Orders")
        .doWrite(exportRows);
  }

  private InAppPurchaseDetailsExportModel toExportModel(InAppPurchaseDetailsCO co) {
    InAppPurchaseDetailsDTO details = co.getDetails();
    InAppPurchaseFactoryDTO factory = Objects.isNull(details) ? null : details.getFactory();
    UserProfileDTO buyer = pickBuyerProfile(co, details);
    UserProfileDTO accept = co.getAcceptUserProfile();

    return new InAppPurchaseDetailsExportModel()
        .setEnv(text(Objects.isNull(details) ? null : details.getEnv()))
        .setSysOrigin(text(Objects.isNull(details) ? null : details.getSysOrigin()))
        .setPlatform(text(Objects.isNull(factory) ? null : factory.getPlatform()))
        .setFactoryCode(text(Objects.isNull(factory) ? null : factory.getFactoryCode()))
        .setChannel(text(Objects.isNull(factory) ? null : factory.getFactoryChannelCode()))
        .setBuyerUserId(userId(buyer, Objects.isNull(details) ? null : details.getCreateUser()))
        .setBuyerAccount(text(Objects.isNull(buyer) ? null : buyer.getAccount()))
        .setBuyerNickname(buyerNickname(co, buyer))
        .setAcceptUserId(userId(accept, Objects.isNull(details) ? null : details.getAcceptUserId()))
        .setAcceptAccount(text(Objects.isNull(accept) ? null : accept.getAccount()))
        .setAcceptNickname(text(Objects.isNull(accept) ? null : accept.getUserNickname()))
        .setAmountUsd(Objects.isNull(details) ? null : details.getAmountUsd())
        .setAmount(Objects.isNull(details) ? null : details.getAmount())
        .setCurrency(text(Objects.isNull(details) ? null : details.getCurrency()))
        .setProductDescriptor(text(Objects.isNull(details) ? null : details.getProductDescriptor()))
        .setStatus(text(Objects.isNull(details) ? null : details.getStatus()))
        .setReceiptType(text(Objects.isNull(details) ? null : details.getReceiptType()))
        .setOrderId(text(Objects.isNull(details) ? null : details.getOrderId()))
        .setAppOrderId(text(Objects.isNull(details) ? null : details.getId()))
        .setTrackId(text(Objects.isNull(details) ? null : details.getTrackId()))
        .setCountryCode(text(Objects.isNull(details) ? null : details.getCountryCode()))
        .setCreateTime(text(Objects.isNull(details) ? null : details.getCreateTime()))
        .setUpdateTime(text(Objects.isNull(details) ? null : details.getUpdateTime()));
  }

  private UserProfileDTO pickBuyerProfile(InAppPurchaseDetailsCO co, InAppPurchaseDetailsDTO details) {
    if (Objects.nonNull(co.getCreateUserProfile())) {
      return co.getCreateUserProfile();
    }
    if (Objects.nonNull(details) && Objects.equals(details.getCreateUser(), details.getAcceptUserId())) {
      return co.getAcceptUserProfile();
    }
    return null;
  }

  private String buyerNickname(InAppPurchaseDetailsCO co, UserProfileDTO buyer) {
    if (StringUtils.isNotBlank(co.getCreateNickname())) {
      return co.getCreateNickname();
    }
    return text(Objects.isNull(buyer) ? null : buyer.getUserNickname());
  }

  private String userId(UserProfileDTO profile, Long fallback) {
    if (Objects.nonNull(profile) && Objects.nonNull(profile.getId())) {
      return Objects.toString(profile.getId());
    }
    return text(fallback);
  }

  private String text(Object value) {
    return Objects.isNull(value) ? "" : Objects.toString(value);
  }

}
