const DatapackLoader = require("./DatapackLoader");
const { PlayerQuest, Player, Inventory, sequelize } = require("../models");

class QuestsManager {
  async onPlayerLogin(playerId, socket = null) {
    try {
      await this.checkAutoQuests(playerId, socket);
      await this.trackProgress(playerId, "LOGIN", null, 1, socket);
    } catch (err) {
      console.error(err);
    }
  }

  async checkAutoQuests(playerId, socket = null) {
    const allQuests = Array.from(DatapackLoader.registry.quests);
    const player = await Player.findByPk(playerId);

    for (const quest of allQuests) {
      if (quest.meta?.autoAccept && player.level >= (quest.minLevel || 0)) {
        const [pq, created] = await PlayerQuest.findOrCreate({
          where: { playerId, questId: quest.id },
          defaults: {
            status: "active",
            progress: quest.objectives.map((obj) => ({
              ...obj,
              currentAmount: 0,
            })),
          },
        });

        if (created && socket) {
          socket.emit("quest:new", {
            id: pq.questId,
            status: pq.status,
            objectives: pq.progress,
          });
        }
      }
    }
  }

  async trackProgress(playerId, type, targetId, amount = 1, socket = null) {
    try {
      const activeQuests = await PlayerQuest.findAll({
        where: { playerId, status: "active" },
      });

      for (const pq of activeQuests) {
        const staticData = DatapackLoader.getQuest(pq.questId);
        if (!staticData) continue;

        let isChanged = false;
        const currentProgress = pq.progress;

        const updatedProgress = currentProgress.map((obj) => {
          if (
            obj.type === type &&
            (obj.targetId === targetId || type === "LOGIN")
          ) {
            if (obj.currentAmount < obj.requiredAmount) {
              obj.currentAmount = Math.min(
                obj.currentAmount + amount,
                obj.requiredAmount,
              );
              isChanged = true;
            }
          }
          return obj;
        });

        if (isChanged) {
          const isReady = updatedProgress.every(
            (obj) => obj.currentAmount >= obj.requiredAmount,
          );

          await pq.update({
            progress: updatedProgress,
            status: isReady ? "ready" : "active",
          });

          if (socket) {
            socket.emit("quest:update", {
              id: pq.questId,
              status: isReady ? "ready" : "active",
              objectives: updatedProgress,
            });
          }

          if (isReady && staticData.meta?.autoComplete) {
            await this.claimRewards(playerId, pq.questId);
          }
        }
      }
    } catch (err) {
      console.error(err);
    }
  }

  async claimRewards(playerId, questId) {
    const t = await sequelize.transaction();

    try {
      const pq = await PlayerQuest.findOne({
        where: { playerId, questId, status: "ready" },
        transaction: t,
        lock: t.LOCK.UPDATE,
      });

      if (!pq) {
        await t.rollback();
        throw new Error("QUEST_NOT_READY_OR_CLAIMED");
      }

      const staticData = DatapackLoader.getQuest(questId);
      const player = await Player.findByPk(playerId, { transaction: t });
      const rewards = staticData.rewards;

      await pq.update({ status: "completed" }, { transaction: t });

      if (rewards.credits) {
        await player.increment("credits", {
          by: rewards.credits,
          transaction: t,
        });
      }

      if (rewards.xp) {
        await player.increment("experience", {
          by: rewards.xp,
          transaction: t,
        });
      }

      if (rewards.items?.length > 0) {
        for (const item of rewards.items) {
          const [invItem] = await Inventory.findOrCreate({
            where: { playerId, itemId: item.id },
            defaults: { quantity: 0 },
            transaction: t,
          });
          await invItem.increment("quantity", {
            by: item.count,
            transaction: t,
          });
        }
      }

      await t.commit();
      const updatedPlayer = await player.reload();

      return {
        success: true,
        rewards,
        newTotalCredits: updatedPlayer.credits,
      };
    } catch (err) {
      if (t) await t.rollback();
      throw err;
    }
  }
}

module.exports = new QuestsManager();
