const { BotMarketplace, User, sequelize } = require("../../models");
const { Op } = require("sequelize");
const { ValidationError, NotFoundError, BadRequestError } = require("../../utils/errors");

const BOT_STATUSES = ["draft", "running", "paused", "stopped", "error"];
const BOT_CATEGORIES = ["grid", "dca", "arbitrage", "scalping", "trend", "custom"];

// Safe JSON stringify (stores TEXT in DB)
const toJsonText = (v) => {
  if (v === null || v === undefined) return null;
  if (typeof v === "string") return v; // assume already stringified
  try {
    return JSON.stringify(v);
  } catch {
    throw new ValidationError("Invalid JSON payload");
  }
};

// Basic validators (backend)
const mustBeUuid = (id, fieldName = "id") => {
  const s = String(id || "");
  // simple UUID v4-ish check (keeps it light)
  const ok = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(s);
  if (!ok) throw new ValidationError(`${fieldName} must be a valid UUID`);
};

class adminBotService {
  async getBots(query = {}) {
    const {
      q,
      status,
      category,
      userId,
      pair,
      exchange,
      page = 1,
      limit = 20,
    } = query;

    const pageNum = Math.max(parseInt(page, 10) || 1, 1);
    const limitNum = Math.min(Math.max(parseInt(limit, 10) || 20, 1), 100);
    const offset = (pageNum - 1) * limitNum;

    const where = {};

    if (status) where.status = status;
    if (category) where.category = category;
    if (pair) where.pair = { [Op.like]: `%${pair}%` };
    if (exchange) where.exchange = { [Op.like]: `%${exchange}%` };

    if (userId) {
      mustBeUuid(userId, "userId");
      where.userId = userId;
    }

    if (q) {
      where[Op.or] = [
        { id: { [Op.like]: `%${q}%` } },
        { name: { [Op.like]: `%${q}%` } },
        { pair: { [Op.like]: `%${q}%` } },
        { exchange: { [Op.like]: `%${q}%` } },
      ];
    }

    const { rows, count } = await BotMarketplace.findAndCountAll({
      where,
      order: [["createdAt", "DESC"]],
      limit: limitNum,
      offset,
    });

    return {
      count,
      page: pageNum,
      limit: limitNum,
      bots: rows.map((b) => ({
        id: b.id,
        userId: b.userId,
        user: b.user
          ? {
              id: b.user.id,
              email: b.user.email,
              name: `${b.user.firstName || ""} ${b.user.lastName || ""}`.trim() || b.user.email,
            }
          : null,
        name: b.name,
        description: b.description,
        category: b.category,
        strategy: b.strategy, // stored as TEXT (JSON string)
        pair: b.pair,
        exchange: b.exchange,
        status: b.status,
        capital: b.capital,
        currentValue: b.currentValue,
        profit: b.profit,
        profitPercent: b.profitPercent,
        totalTrades: b.totalTrades,
        winningTrades: b.winningTrades,
        losingTrades: b.losingTrades,
        winRate: b.winRate,
        maxDrawdown: b.maxDrawdown,
        runtime: b.runtime,
        startedAt: b.startedAt,
        stoppedAt: b.stoppedAt,
        lastTradeAt: b.lastTradeAt,
        config: b.config,
        riskSettings: b.riskSettings,
        errorMessage: b.errorMessage,
        performanceData: b.performanceData,
        createdAt: b.createdAt,
        updatedAt: b.updatedAt,
      })),
    };
  }

  async getBotById(botId) {
    mustBeUuid(botId, "botId");

    const bot = await Bot.findByPk(botId, {
      include: [
        {
          model: User,
          as: "user",
          attributes: ["id", "email", "firstName", "lastName"],
          required: false,
        },
      ],
    });

    if (!bot) throw new NotFoundError("Bot not found");

    return { bot };
  }

  async createBot(payload = {}, adminId = null) {
    const {
      userId,
      name,
      description,
      category,
      strategy,
      pair,
      exchange = "binance",
      status = "draft",
      capital = 0,
      currentValue = 0,
      profit = 0,
      profitPercent = 0,
      totalTrades = 0,
      winningTrades = 0,
      losingTrades = 0,
      winRate = 0,
      maxDrawdown = 0,
      runtime = 0,
      startedAt = null,
      stoppedAt = null,
      lastTradeAt = null,
      config = null,
      riskSettings = null,
      errorMessage = null,
      performanceData = null,
    } = payload;

    if (!userId) throw new BadRequestError("userId is required");
    mustBeUuid(userId, "userId");

    if (!name || String(name).trim().length < 2) throw new ValidationError("name is required (min 2 chars)");
    if (!pair || String(pair).trim().length < 3) throw new ValidationError("pair is required");
    if (!category || !BOT_CATEGORIES.includes(String(category))) {
      throw new ValidationError(`category must be one of: ${BOT_CATEGORIES.join(", ")}`);
    }
    if (!strategy) throw new ValidationError("strategy is required");

    if (status && !BOT_STATUSES.includes(String(status))) {
      throw new ValidationError(`status must be one of: ${BOT_STATUSES.join(", ")}`);
    }

    const user = await User.findByPk(userId);
    if (!user) throw new NotFoundError("User not found");

    const bot = await Bot.create({
      userId,
      name: String(name).trim(),
      description: description ?? null,
      category,
      strategy: toJsonText(strategy),
      pair: String(pair).trim(),
      exchange: String(exchange || "binance").trim(),
      status,

      capital,
      currentValue,
      profit,
      profitPercent,
      totalTrades,
      winningTrades,
      losingTrades,
      winRate,
      maxDrawdown,
      runtime,
      startedAt,
      stoppedAt,
      lastTradeAt,

      config: toJsonText(config),
      riskSettings: toJsonText(riskSettings),
      errorMessage,
      performanceData: toJsonText(performanceData),
      // you can also store adminId in metadata if you have a column for it
    });

    return bot;
  }

  async updateBot(botId, payload = {}, adminId = null) {
    mustBeUuid(botId, "botId");

    const bot = await Bot.findByPk(botId);
    if (!bot) throw new NotFoundError("Bot not found");

    // Optional: allow reassign user
    if (payload.userId) {
      mustBeUuid(payload.userId, "userId");
      const user = await User.findByPk(payload.userId);
      if (!user) throw new NotFoundError("User not found");
    }

    if (payload.category && !BOT_CATEGORIES.includes(String(payload.category))) {
      throw new ValidationError(`category must be one of: ${BOT_CATEGORIES.join(", ")}`);
    }

    if (payload.status && !BOT_STATUSES.includes(String(payload.status))) {
      throw new ValidationError(`status must be one of: ${BOT_STATUSES.join(", ")}`);
    }

    const next = { ...payload };

    // stringify JSON fields if needed
    if ("strategy" in next) next.strategy = toJsonText(next.strategy);
    if ("config" in next) next.config = toJsonText(next.config);
    if ("riskSettings" in next) next.riskSettings = toJsonText(next.riskSettings);
    if ("performanceData" in next) next.performanceData = toJsonText(next.performanceData);

    await bot.update(next);
    return bot;
  }

  async deleteBot(botId, adminId = null) {
    mustBeUuid(botId, "botId");

    const bot = await Bot.findByPk(botId);
    if (!bot) throw new NotFoundError("Bot not found");

    await bot.destroy();
    return { deleted: true, id: botId };
  }

  async updateBotStatus(botId, status, adminId = null) {
    mustBeUuid(botId, "botId");
    if (!status) throw new ValidationError("status is required");
    if (!BOT_STATUSES.includes(String(status))) {
      throw new ValidationError(`status must be one of: ${BOT_STATUSES.join(", ")}`);
    }

    return await sequelize.transaction(async (t) => {
      const bot = await Bot.findByPk(botId, { transaction: t, lock: t.LOCK.UPDATE });
      if (!bot) throw new NotFoundError("Bot not found");

      const updates = { status: String(status) };

      if (status === "running" && !bot.startedAt) updates.startedAt = new Date();
      if (["stopped", "error"].includes(status)) updates.stoppedAt = new Date();

      await bot.update(updates, { transaction: t });
      return bot;
    });
  }
}

module.exports = new adminBotService();