"use strict";

const { Op } = require("sequelize");
const {
  sequelize,
  BotMarketplace,
  BotInstance,
  BotOrder,
  BotEvent,
  BotPosition,
  UserBalance,
} = require("../models");

const { NotFoundError, BadRequestError } = require("../utils/errors");
const { computeBotEndDate } = require("../utils/helpers");

// Optional but recommended if you have strategies
let strategyRegistry = null;
try {
  ({ strategyRegistry } = require("../strategies"));
} catch (_) {
  strategyRegistry = null;
}

class BotService {
  // ---------------------------
  // Marketplace
  // ---------------------------

  async marketplace(userId, filters = {}) {
    const {
      search,
      category,
      tier,
      sortBy = "popular",
      page = 1,
      limit = 20,
    } = filters;

    const offset = (Number(page) - 1) * Number(limit);

    const where = { isPublished: true };
    if (category) where.category = category;
    if (tier) where.tier = tier;

    // MySQL: use LIKE
    if (search) where.name = { [Op.like]: `%${search}%` };

    const sortMap = {
      popular: [["downloads", "DESC"]],
      newest: [["createdAt", "DESC"]],
      oldest: [["createdAt", "ASC"]],
      rating: [["rating", "DESC"]],
      featured: [["isFeatured", "DESC"], ["downloads", "DESC"]],
    };

    const order = sortMap[sortBy] ?? [["createdAt", "DESC"]];

    const [botsResult, totalCount, myInstances, myInstancesCount] = await Promise.all([
      BotMarketplace.findAndCountAll({
        where,
        order,
        limit: Number(limit),
        offset,
      }),
      BotMarketplace.count({ where }),
      BotInstance.findAndCountAll({ where: { userId } }),
      BotInstance.count({ where: { userId } }),
    ]);

    return {
      stats: {
        activeBots: myInstancesCount,
        totalBots: totalCount,
        totalBotTrade: 0,
        averageWinRate: 0,
      },
      pagination: {
        page: Number(page),
        limit: Number(limit),
        total: botsResult.count,
        pages: Math.ceil(botsResult.count / Number(limit)),
      },
      bots: botsResult.rows,
      myBots: myInstances.rows,
    };
  }

  async getMarketplaceBotDetails(userId, marketplaceBotId) {
    const bot = await BotMarketplace.findOne({
      where: { id: marketplaceBotId, isPublished: true },
    });
    if (!bot) throw new NotFoundError("Bot not found");

    // User entitlement = has active instance
    const activeInstance = await BotInstance.findOne({
      where: {
        userId,
        marketplaceBotId,
        billingStatus: "active",
      },
      order: [["createdAt", "DESC"]],
    });

    const botData = bot.toJSON();
    botData.isSubscribed = !!activeInstance;

    // Hide strategy code unless subscribed
    if (!activeInstance) delete botData.strategyCode;

    return botData;
  }

  // ---------------------------
  // Deploy (creates BotInstance)
  // ---------------------------

  async deployBot(userId, marketplaceBotId, payload = {}) {
    const { exchangeAccountId, symbol, config = {}, autoRenew = true } = payload;

    //if (!exchangeAccountId) throw new BadRequestError("exchangeAccountId is required");
    //if (!symbol) throw new BadRequestError("symbol is required");

    const bot = await BotMarketplace.findOne({
      where: { id: marketplaceBotId, isPublished: true },
    });
    if (!bot) throw new NotFoundError("Bot not found");

    // Prevent duplicates for same marketplace+symbol+exchange
    const existing = await BotInstance.findOne({
      where: {
        userId,
        marketplaceBotId,
        exchangeAccountId,
        symbol,
        status: { [Op.in]: ["running", "paused", "stopped"] },
      },
    });
    if (existing) throw new BadRequestError("You already deployed this bot for this symbol/exchange");

    // Merge default config + user config
    const mergedConfig = { ...(bot.defaultConfig || {}), ...(config || {}) };

    // Validate/init state via strategy (if available)
    const strategy = strategyRegistry?.get?.(bot.category);
    if (strategy?.validateConfig) {
      const v = strategy.validateConfig(mergedConfig);
      if (!v.ok) throw new BadRequestError(v.message || "Invalid config");
    }
    const initialState = strategy?.initState ? strategy.initState(mergedConfig) : {};

    // Billing setup
    const botPrice = Number(bot.price || 0);
    if (Number.isNaN(botPrice)) throw new BadRequestError("Invalid bot price");

    const startDate = new Date();
    const endDate = computeBotEndDate(startDate, bot.pricePeriod); // handles lifetime
    const nextPaymentDate = bot.pricePeriod === "lifetime" ? null : endDate;

    const tx = await sequelize.transaction();
    try {
      // Balance check/deduct if paid
      if (botPrice > 0) {
        const userBalance = await UserBalance.findOne({
          where: { userId },
          transaction: tx,
          lock: tx.LOCK.UPDATE,
        });
        if (!userBalance) throw new BadRequestError("User balance not found");

        const balance = Number(userBalance.balance || 0);
        if (balance < botPrice) throw new BadRequestError("Insufficient balance");

        await userBalance.decrement("balance", { by: botPrice, transaction: tx });
      }

      const instance = await BotInstance.create(
        {
          userId,
          marketplaceBotId,
          exchangeAccountId,
          symbol,

          status: "running",
          config: mergedConfig,
          state: initialState,

          billingStatus: "active",
          startDate,
          endDate,
          autoRenew,
          price: botPrice,
          pricePeriod: bot.pricePeriod,
          lastPaymentDate: botPrice > 0 ? new Date() : null,
          nextPaymentDate,
        },
        { transaction: tx }
      );

      await bot.increment("activeInstances", { by: 1, transaction: tx });
      await bot.increment("users", { by: 1, transaction: tx });
      await bot.increment("downloads", { by: 1, transaction: tx });

      await tx.commit();
      return instance;
    } catch (err) {
      await tx.rollback();
      throw err;
    }
  }

  // ---------------------------
  // Instances list & details
  // ---------------------------

  async getMyBots(userId) {
    return await BotInstance.findAll({
      where: { userId },
      order: [["createdAt", "DESC"]],
      include: [
        { model: BotMarketplace, as: "bot", required: false, paranoid: false },
      ],
    });
  }

  async getBotInstanceDetails(userId, instanceId) {
    const instance = await BotInstance.findOne({
      where: { id: instanceId, userId },
      include: [
        { model: BotMarketplace, as: "bot", required: false, paranoid: false },
        { model: BotOrder, as: "botOrders", required: false, separate: true, order: [["createdAt", "DESC"]], limit: 50 },
        { model: BotPosition, as: "botPositions", required: false },
      ],
    });

    if (!instance) throw new NotFoundError("Bot instance not found");
    return instance;
  }

  // ---------------------------
  // Lifecycle controls
  // ---------------------------

  async startBot(userId, instanceId) {
    const tx = await sequelize.transaction();
    try {
      const instance = await this._getOwnedInstance(userId, instanceId, tx);

      this._ensureActiveBilling(instance);

      if (instance.status === "running") throw new BadRequestError("Bot is already running");

      await instance.update(
        { status: "running", errorCode: null, errorMessage: null, lastHeartbeatAt: new Date() },
        { transaction: tx }
      );

      // optional: enqueue worker command
      // await this.enqueueCommand(instance.id, "start");

      await tx.commit();
      return instance;
    } catch (err) {
      await tx.rollback();
      throw err;
    }
  }

  async stopBot(userId, instanceId) {
    const tx = await sequelize.transaction();
    try {
      const instance = await this._getOwnedInstance(userId, instanceId, tx);

      if (instance.status === "stopped") throw new BadRequestError("Bot is already stopped");

      await instance.update({ status: "stopped" }, { transaction: tx });

      // optional: enqueue worker command
      // await this.enqueueCommand(instance.id, "stop");

      await tx.commit();
      return instance;
    } catch (err) {
      await tx.rollback();
      throw err;
    }
  }

  async pauseBot(userId, instanceId) {
    const tx = await sequelize.transaction();
    try {
      const instance = await this._getOwnedInstance(userId, instanceId, tx);

      if (instance.status !== "running") throw new BadRequestError("Bot must be running to pause");

      await instance.update({ status: "paused" }, { transaction: tx });

      // optional: enqueue worker command
      // await this.enqueueCommand(instance.id, "pause");

      await tx.commit();
      return instance;
    } catch (err) {
      await tx.rollback();
      throw err;
    }
  }

  async resumeBot(userId, instanceId) {
    const tx = await sequelize.transaction();
    try {
      const instance = await this._getOwnedInstance(userId, instanceId, tx);

      this._ensureActiveBilling(instance);

      if (instance.status !== "paused") throw new BadRequestError("Bot is not paused");

      await instance.update({ status: "running" }, { transaction: tx });

      // optional: enqueue worker command
      // await this.enqueueCommand(instance.id, "resume");

      await tx.commit();
      return instance;
    } catch (err) {
      await tx.rollback();
      throw err;
    }
  }

  // ---------------------------
  // Config update
  // ---------------------------

  async updateBotConfig(userId, instanceId, payload = {}) {
    const { config } = payload;
    if (!config || typeof config !== "object") throw new BadRequestError("config is required");

    const tx = await sequelize.transaction();
    try {
      const instance = await this._getOwnedInstance(userId, instanceId, tx);

      if (instance.status === "running") throw new BadRequestError("Stop bot before updating config");

      const bot = await BotMarketplace.findByPk(instance.marketplaceBotId, { transaction: tx });
      if (!bot) throw new NotFoundError("Marketplace bot not found");

      const mergedConfig = { ...(bot.defaultConfig || {}), ...(config || {}) };

      const strategy = strategyRegistry?.get?.(bot.category);
      if (strategy?.validateConfig) {
        const v = strategy.validateConfig(mergedConfig);
        if (!v.ok) throw new BadRequestError(v.message || "Invalid config");
      }

      await instance.update({ config: mergedConfig }, { transaction: tx });

      // optional: enqueue update config
      // await this.enqueueCommand(instance.id, "update_config", { config: mergedConfig });

      await tx.commit();
      return instance;
    } catch (err) {
      await tx.rollback();
      throw err;
    }
  }

  // ---------------------------
  // Delete instance
  // ---------------------------

  async deleteBot(userId, instanceId) {
    const tx = await sequelize.transaction();
    try {
      const instance = await this._getOwnedInstance(userId, instanceId, tx);

      if (instance.status === "running") throw new BadRequestError("Stop the bot before deleting");

      await instance.destroy({ transaction: tx });

      // reduce activeInstances (best-effort)
      await BotMarketplace.increment(
        { activeInstances: -1 },
        { where: { id: instance.marketplaceBotId }, transaction: tx }
      ).catch(() => {});

      await tx.commit();
      return true;
    } catch (err) {
      await tx.rollback();
      throw err;
    }
  }

  // ---------------------------
  // Stats / Orders / Events
  // ---------------------------

  async getBotStats(userId, instanceId, timeframe = "30d") {
    await this._getOwnedInstance(userId, instanceId);

    const position = await BotPosition.findOne({
      where: { botInstanceId: instanceId },
    });

    const totalOrders = await BotOrder.count({
      where: { botInstanceId: instanceId },
    });

    return {
      timeframe,
      totalOrders,
      position: position ? position.toJSON() : null,
    };
  }

  async getBotOrders(userId, instanceId, { page = 1, limit = 50 } = {}) {
    await this._getOwnedInstance(userId, instanceId);

    const offset = (Number(page) - 1) * Number(limit);

    const result = await BotOrder.findAndCountAll({
      where: { botInstanceId: instanceId },
      order: [["createdAt", "DESC"]],
      limit: Number(limit),
      offset,
    });

    return {
      pagination: {
        page: Number(page),
        limit: Number(limit),
        total: result.count,
        pages: Math.ceil(result.count / Number(limit)),
      },
      orders: result.rows,
    };
  }

  async getBotEvents(userId, instanceId, { page = 1, limit = 100 } = {}) {
    await this._getOwnedInstance(userId, instanceId);

    const offset = (Number(page) - 1) * Number(limit);

    const result = await BotEvent.findAndCountAll({
      where: { botInstanceId: instanceId },
      order: [["createdAt", "DESC"]],
      limit: Number(limit),
      offset,
    });

    return {
      pagination: {
        page: Number(page),
        limit: Number(limit),
        total: result.count,
        pages: Math.ceil(result.count / Number(limit)),
      },
      events: result.rows,
    };
  }

  // ---------------------------
  // Review / rating
  // ---------------------------

  async rateMarketplaceBot(userId, marketplaceBotId, { rating, review }) {
    if (!rating || rating < 1 || rating > 5) throw new BadRequestError("Rating must be between 1 and 5");

    const tx = await sequelize.transaction();
    try {
      // must have deployed at least one instance
      const instance = await BotInstance.findOne({
        where: { userId, marketplaceBotId },
        order: [["createdAt", "DESC"]],
        transaction: tx,
        lock: tx.LOCK.UPDATE,
      });

      if (!instance) throw new BadRequestError("Deploy this bot before reviewing");

      await instance.update(
        { userRating: rating, userReview: review || null, reviewedAt: new Date() },
        { transaction: tx }
      );

      // recompute marketplace rating from instances
      const ratings = await BotInstance.findAll({
        where: { marketplaceBotId, userRating: { [Op.not]: null } },
        attributes: ["userRating"],
        transaction: tx,
      });

      const avg = ratings.length
        ? ratings.reduce((s, r) => s + Number(r.userRating), 0) / ratings.length
        : 0;

      await BotMarketplace.update(
        { rating: avg.toFixed(2), reviews: ratings.length },
        { where: { id: marketplaceBotId }, transaction: tx }
      );

      await tx.commit();
      return { rating: avg.toFixed(2), reviews: ratings.length };
    } catch (err) {
      await tx.rollback();
      throw err;
    }
  }

  // ---------------------------
  // Billing helpers (optional endpoints)
  // ---------------------------

  async cancelAutoRenew(userId, instanceId) {
    const instance = await this._getOwnedInstance(userId, instanceId);
    await instance.update({ autoRenew: false });
    return instance;
  }

  async renewSubscription(userId, instanceId) {
    const tx = await sequelize.transaction();
    try {
      const instance = await this._getOwnedInstance(userId, instanceId, tx);

      const bot = await BotMarketplace.findByPk(instance.marketplaceBotId, { transaction: tx });
      if (!bot) throw new NotFoundError("Marketplace bot not found");

      const price = Number(bot.price || 0);

      if (price > 0) {
        const userBalance = await UserBalance.findOne({
          where: { userId },
          transaction: tx,
          lock: tx.LOCK.UPDATE,
        });
        if (!userBalance) throw new BadRequestError("User balance not found");

        const balance = Number(userBalance.balance || 0);
        if (balance < price) throw new BadRequestError("Insufficient balance");

        await userBalance.decrement("balance", { by: price, transaction: tx });
      }

      const startDate = new Date();
      const endDate = computeBotEndDate(startDate, bot.pricePeriod);
      const nextPaymentDate = bot.pricePeriod === "lifetime" ? null : endDate;

      await instance.update(
        {
          billingStatus: "active",
          startDate,
          endDate,
          lastPaymentDate: price > 0 ? new Date() : instance.lastPaymentDate,
          nextPaymentDate,
          price,
          pricePeriod: bot.pricePeriod,
        },
        { transaction: tx }
      );

      await tx.commit();
      return instance;
    } catch (err) {
      await tx.rollback();
      throw err;
    }
  }

  // ---------------------------
  // Internal helpers
  // ---------------------------

  async _getOwnedInstance(userId, instanceId, tx = null) {
    const instance = await BotInstance.findOne({
      where: { id: instanceId, userId },
      transaction: tx ?? undefined,
      lock: tx ? tx.LOCK.UPDATE : undefined,
    });

    if (!instance) throw new NotFoundError("Bot instance not found");
    return instance;
  }

  _ensureActiveBilling(instance) {
    if (instance.billingStatus && instance.billingStatus !== "active") {
      throw new BadRequestError("Subscription not active");
    }
    const now = new Date();
    if (instance.endDate && new Date(instance.endDate) <= now) {
      throw new BadRequestError("Subscription has expired");
    }
  }
}

module.exports = new BotService();
