package emu.grasscutter.game.activity;

import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ActivityWatcherData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.ActivityWatcherInfoOuterClass;
import emu.grasscutter.server.packet.send.PacketActivityUpdateWatcherNotify;
import emu.grasscutter.utils.JsonUtils;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Entity("activities")
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class PlayerActivityData {
    @Id
    String id;
    int uid;
    int activityId;
    Map<Integer, WatcherInfo> watcherInfoMap;
    /**
     * the detail data of each type of activity (Json format)
     */
    String detail;
    @Transient Player player;
    @Transient ActivityHandler activityHandler;
    public void save(){
        DatabaseHelper.savePlayerActivityData(this);
    }

    public static PlayerActivityData getByPlayer(Player player, int activityId){
        return DatabaseHelper.getPlayerActivityData(player.getUid(), activityId);
    }

    public synchronized void addWatcherProgress(int watcherId){
        var watcherInfo = watcherInfoMap.get(watcherId);
        if(watcherInfo == null){
            return;
        }

        if(watcherInfo.curProgress >= watcherInfo.totalProgress){
            return;
        }

        watcherInfo.curProgress++;
        getPlayer().sendPacket(new PacketActivityUpdateWatcherNotify(activityId, watcherInfo));
    }

    public List<ActivityWatcherInfoOuterClass.ActivityWatcherInfo> getAllWatcherInfoList() {
        return watcherInfoMap.values().stream()
            .map(WatcherInfo::toProto)
            .toList();
    }

    public void setDetail(Object detail){
        this.detail = JsonUtils.encode(detail);
    }

    public void takeWatcherReward(int watcherId) {
        var watcher = watcherInfoMap.get(watcherId);
        if(watcher == null || watcher.isTakenReward()){
            return;
        }

        var reward = Optional.of(watcher)
            .map(WatcherInfo::getMetadata)
            .map(ActivityWatcherData::getRewardID)
            .map(id -> GameData.getRewardDataMap().get(id.intValue()));

        if(reward.isEmpty()){
            return;
        }

        List<GameItem> rewards = new ArrayList<>();
        for (ItemParamData param : reward.get().getRewardItemList()) {
            rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
        }

        player.getInventory().addItems(rewards, ActionReason.ActivityWatcher);
        watcher.setTakenReward(true);
        save();
    }

    @Entity
    @Data
    @FieldDefaults(level = AccessLevel.PRIVATE)
    @Builder(builderMethodName = "of")
    public static class WatcherInfo{
        int watcherId;
        int totalProgress;
        int curProgress;
        boolean isTakenReward;

        public ActivityWatcherData getMetadata(){
            return GameData.getActivityWatcherDataMap().get(watcherId);
        }

        public static WatcherInfo init(ActivityWatcher watcher){
            return WatcherInfo.of()
                .watcherId(watcher.getWatcherId())
                .totalProgress(watcher.getActivityWatcherData().getProgress())
                .isTakenReward(false)
                .build();
        }

        public ActivityWatcherInfoOuterClass.ActivityWatcherInfo toProto(){
            return ActivityWatcherInfoOuterClass.ActivityWatcherInfo.newBuilder()
                .setWatcherId(watcherId)
                .setCurProgress(curProgress)
                .setTotalProgress(totalProgress)
                .setIsTakenReward(isTakenReward)
                .build();
        }
    }
}
