package emu.nebula.game.battlepass;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;

import emu.nebula.GameConstants;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.data.resources.BattlePassRewardDef;
import emu.nebula.database.GameDatabaseObject;
import emu.nebula.game.inventory.ItemParamMap;
import emu.nebula.game.player.Player;
import emu.nebula.game.player.PlayerChangeInfo;
import emu.nebula.game.quest.GameQuest;
import emu.nebula.game.quest.QuestCondType;
import emu.nebula.game.quest.QuestType;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.BattlePassInfoOuterClass.BattlePassInfo;
import emu.nebula.util.Bitset;

import lombok.Getter;

@Getter
@Entity(value = "battlepass", useDiscriminator = false)
public class BattlePass implements GameDatabaseObject {
    @Id
    private int uid;
    private transient BattlePassManager manager;
    
    private int battlePassId;
    private int mode;
    private int level;
    private int exp;
    private int expWeek;
    
    private Bitset basicReward;
    private Bitset premiumReward;
    
    private Map<Integer, GameQuest> quests;
    
    @Deprecated // Morphia only
    public BattlePass() {
        
    }
    
    public BattlePass(BattlePassManager manager) {
        this.uid = manager.getPlayerUid();
        this.manager = manager;
        this.battlePassId = GameConstants.BATTLE_PASS_ID;
        this.basicReward = new Bitset();
        this.premiumReward = new Bitset();
        
        // Setup battle pass quests
        this.quests = new HashMap<>();
        for (var data : GameData.getBattlePassQuestDataTable()) {
            this.quests.put(data.getId(), new GameQuest(data));
        }
        
        // Save to database
        this.save();
    }

    public void setManager(BattlePassManager manager) {
        this.manager = manager;
    }
    
    public Player getPlayer() {
        return manager.getPlayer();
    }
    
    public boolean isPremium() {
        return this.mode > 0;
    }

    private BattlePassRewardDef getRewardData(int level) {
        return GameData.getBattlePassRewardDataTable().get((this.getBattlePassId() << 16) + level);
    }
    
    public int getMaxExp() {
        var data = GameData.getBattlePassLevelDataTable().get(this.getLevel() + 1);
        return data != null ? data.getExp() : 0; 
    }
    
    public void addExp(int amount) {
        // Setup
        int expRequired = this.getMaxExp();

        // Add exp
        this.exp += amount;

        // Check for level ups
        while (this.exp >= expRequired && expRequired > 0) {
            this.level += 1;
            this.exp -= expRequired;
            
            expRequired = this.getMaxExp();
        }
    }
    
    public synchronized void resetDailyQuests(boolean resetWeekly) {
        // Reset daily quests
        for (var data : GameData.getBattlePassQuestDataTable()) {
            // Get quest
            var quest = getQuests().computeIfAbsent(data.getId(), i -> new GameQuest(data));
            
            // Don't reset weekly quests
            if (!data.isDaily() && !resetWeekly) {
                continue;
            }
            
            // Reset progress
            quest.resetProgress();
            
            // Sync quest with player client
            this.syncQuest(quest);
        }
        
        // Persist to database
        this.save();
    }
    
    public synchronized void trigger(QuestCondType condition, int progress, int param) {
        for (var quest : getQuests().values()) {
            // Try to trigger quest
            boolean result = quest.trigger(condition, progress, param);
            
            // Skip if quest progress wasn't changed
            if (!result) {
                continue;
            }
            
            // Sync quest with player client
            this.syncQuest(quest);
            
            // Update in database
            Nebula.getGameDatabase().update(this, this.getUid(), "quests." + quest.getId(), quest);
        }
    }
    
    /**
     * Update this quest on the player client
     */
    private void syncQuest(GameQuest quest) {
        if (!getPlayer().hasSession()) {
            return;
        }
        
        getPlayer().addNextPackage(
            NetMsgId.quest_change_notify, 
            quest.toProto()
        );
    }
    
    public BattlePass receiveQuestReward(int questId) {
        // Get received quests
        var claimList = new ArrayList<GameQuest>();
        
        if (questId > 0) {
            // Claim specific quest
            var quest = this.getQuests().get(questId);
            
            if (quest != null && !quest.isClaimed()) {
                claimList.add(quest);
            }
        } else {
            // Claim all
            for (var quest : this.getQuests().values()) {
                if (!quest.isComplete() || quest.isClaimed()) {
                    continue;
                }
                
                claimList.add(quest);
            }
        }
        
        // Sanity check
        if (claimList.isEmpty()) {
            return null;
        }
        
        // Init exp
        int exp = 0;
        int expWeek = 0;
        
        // Claim
        for (var quest : claimList) {
            // Get data
            var data = GameData.getBattlePassQuestDataTable().get(quest.getId());
            if (data == null) {
                continue;
            }
            
            // Set claimed
            quest.setClaimed(true);
            
            // Add exp
            exp += data.getExp();
            
            // Check if quest is weekly
            if (quest.getType() == QuestType.BattlePassWeekly) {
                expWeek += data.getExp();
            }
        }
        
        // Add exp
        if (exp > 0) {
            this.addExp(exp);
        }
        
        if (expWeek > 0) {
            this.expWeek += expWeek;
        }
        
        // Save to database
        this.save();
        
        // Success
        return this;
    }
    
    public PlayerChangeInfo receiveReward(boolean premium, int levelId) {
        // Get bitset
        Bitset rewards = null;
        
        if (premium) {
            rewards = this.getPremiumReward();
        } else {
            rewards = this.getBasicReward();
        }
        
        // Make sure we haven't already claimed the reward
        if (rewards.isSet(levelId)) {
            return null;
        }
        
        // Set claimed
        rewards.setBit(levelId);
        
        // Save to database
        this.save();
        
        // Get reward data
        var data = this.getRewardData(levelId);
        if (data == null) {
            return new PlayerChangeInfo();
        }
        
        // Add items
        if (premium) {
            return getPlayer().getInventory().addItems(data.getPremiumRewards());
        } else {
            return getPlayer().getInventory().addItems(data.getBasicRewards());
        }
    }
    
    public PlayerChangeInfo receiveReward() {
        // Init rewards
        var rewards = new ItemParamMap();
        
        // Get unclaimed rewards
        for (int i = 1; i <= this.getLevel(); i++) {
            // Cache reward data
            BattlePassRewardDef data = null;
            
            // Basic reward
            if (!this.getBasicReward().isSet(i)) {
                // Set flag
                this.getBasicReward().setBit(i);
                
                // Get reward data if we havent already
                if (data == null) {
                    data = this.getRewardData(i);
                }
                
                // Add basic rewards
                if (data != null) {
                    rewards.add(data.getBasicRewards());
                }
            }
            
            // Premium reward
            if (this.isPremium() && !this.getPremiumReward().isSet(i)) {
                // Set flag
                this.getPremiumReward().setBit(i);
                
                // Get reward data if we havent already
                if (data == null) {
                    data = this.getRewardData(i);
                }
                
                // Add basic rewards
                if (data != null) {
                    rewards.add(data.getPremiumRewards());
                }
            }
        }
        
        // Save if we have any rewards to add
        if (rewards.size() > 0) {
            this.save();
        } else {
            return null;
        }
        
        // Add rewards
        return getPlayer().getInventory().addItems(rewards);
    }

    // Proto
    
    public BattlePassInfo toProto() {
        var proto = BattlePassInfo.newInstance()
                .setId(this.getBattlePassId())
                .setLevel(this.getLevel())
                .setMode(this.getMode())
                .setExp(this.getExp())
                .setExpThisWeek(this.getExpWeek())
                .setDeadline(Long.MAX_VALUE)
                .setBasicReward(this.getBasicReward().toByteArray())
                .setPremiumReward(this.getPremiumReward().toByteArray());
        
        var daily = proto.getMutableDailyQuests();
        var weekly = proto.getMutableWeeklyQuests();
        
        for (var quest : this.getQuests().values()) {
            if (quest.getType() == QuestType.BattlePassDaily) {
                daily.addList(quest.toProto());
            } else if (quest.getType() == QuestType.BattlePassWeekly) {
                weekly.addList(quest.toProto());
            }
        }
        
        return proto;
    }
}
