const { TraderProfile, CopyRelation, User, sequelize, MarketPair, CopyTrade, Asset } = require("../../models");
const { Op } = require("sequelize");
const { ValidationError, NotFoundError, BadRequestError, ConflictError } = require("../../utils/errors");
const { logging } = require("../../config");
const logger = require("../../utils/logger");

const toNum = (v, def = null) => {
  if (v === "" || v === undefined || v === null) return def;
  const n = Number(v);
  return Number.isFinite(n) ? n : def;
};

class adminCopyTradingService {
  // -------------------------
  // TRADER PROFILES
  // -------------------------
  async listTraders(query = {}) {
    const page = Math.max(parseInt(query.page || "1", 10), 1);
    const limit = Math.min(Math.max(parseInt(query.limit || "20", 10), 1), 100);
    const offset = (page - 1) * limit;

    const where = {};
    if (query.status) where.status = query.status;
    if (query.badge) where.badge = query.badge;
    if (query.riskLevel) where.riskLevel = query.riskLevel;
    if (query.isVerified === "true") where.isVerified = true;
    if (query.isVerified === "false") where.isVerified = false;

    if (query.q) {
      const q = String(query.q).trim();
      where[Op.or] = [
        { displayName: { [Op.like]: `%${q}%` } },
        { tradingStyle: { [Op.like]: `%${q}%` } },
      ];
    }

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

    return { count, page, limit, traders: rows };
  }

  async getTraderById(id) {
    const trader = await TraderProfile.findByPk(id);
    if (!trader) throw new NotFoundError("Trader profile not found");
    return { trader };
  }

  async createTrader(data, file, adminId = null) {
    const displayName = String(data.displayName || "").trim();
    if (!displayName) throw new ValidationError("displayName is required");
    let winRate = 0;
    // normalize rating & winRate if needed
    if (data.totalTrades > 0) {
      winRate = Number(
        ((data.successfulTrades / data.totalTrades) * 100).toFixed(2)
      );
    }

    const payload = {
      displayName,
      bio: data.bio || null,
      tradingStyle: data.tradingStyle || null,
      markets: Array.isArray(data.markets) ? data.markets : (data.markets ? [data.markets] : []),
      isVerified: !!data.isVerified,
      badge: data.badge || "none",
      riskLevel: data.riskLevel || "medium",
      totalFollowers: toNum(data.totalFollowers, 0),
      totalCopiers: toNum(data.totalCopiers, 0),
      totalTrades: toNum(data.totalTrades, 0),
      successfulTrades: toNum(data.successfulTrades, 0),
      winRate: toNum(winRate, 0),
      profit7d: toNum(data.profit7d, 0),
      profit30d: toNum(data.profit30d, 0),
      profit90d: toNum(data.profit90d, 0),
      profitAll: toNum(data.profitAll, 0),
      maxDrawdown: toNum(data.maxDrawdown, 0),
      avgTradeSize: toNum(data.avgTradeSize, 0),
      minCopyAmount: toNum(data.minCopyAmount, 100),
      performanceFee: toNum(data.performanceFee, 0.2),
      acceptingCopiers: data.acceptingCopiers === undefined ? true : !!data.acceptingCopiers,
      status: data.status || "active",
      metadata: data.metadata && typeof data.metadata === "object" ? data.metadata : {},
    };

    if (file) payload.avatar = file;

    return await sequelize.transaction(async (t) => {
      const trader = await TraderProfile.create(payload, { transaction: t });
      // optional: audit metadata
      await trader.update(
        { metadata: { ...(trader.metadata || {}), created_by_admin: adminId } },
        { transaction: t }
      );
      return trader;
    });
  }

  async updateTrader(id, data,file, adminId = null) {
    const trader = await TraderProfile.findByPk(id);
    if (!trader) throw new NotFoundError("Trader profile not found");

    if (data.displayName !== undefined) {
      const displayName = String(data.displayName || "").trim();
      if (!displayName) throw new ValidationError("displayName cannot be empty");
    }

    const patch = { ...data };

    if (patch.totalTrades > 0) {
      patch.winRate = Number(
        ((patch.successfulTrades / patch.totalTrades) * 100).toFixed(2)
      );
    }

    // normalize markets
    if (patch.markets !== undefined) {
      patch.markets = Array.isArray(patch.markets)
        ? patch.markets
        : (patch.markets ? [patch.markets] : []);
    }

    

    // numeric normalization
    const numericFields = [
      "totalFollowers",
      "totalCopiers",
      "totalTrades",
      "winRate",
      "profit7d",
      "profit30d",
      "profit90d",
      "profitAll",
      "maxDrawdown",
      "avgTradeSize",
      "minCopyAmount",
      "performanceFee",
    ];
    for (const f of numericFields) {
      if (patch[f] !== undefined) patch[f] = toNum(patch[f], trader[f]);
    }


    if (patch.metadata !== undefined) {
      patch.metadata =
        patch.metadata && typeof patch.metadata === "object"
          ? patch.metadata
          : trader.metadata || {};
      patch.metadata = { ...patch.metadata, updated_by_admin: adminId };
    } else {
      patch.metadata = { ...(trader.metadata || {}), updated_by_admin: adminId };
    }
    if (file) patch.avatar = file;

    await trader.update(patch);
    return trader;
  }

  async deleteTrader(id, adminId = null) {
    const trader = await TraderProfile.findByPk(id);
    if (!trader) throw new NotFoundError("Trader profile not found");

    // prevent delete if relations exist (safer)
    const relCount = await CopyRelation.count({ where: { traderProfileId: id } });
    if (relCount > 0) {
      throw new ConflictError("Cannot delete trader profile with existing copy relations");
    }

    await trader.destroy();
    return { deleted: true, id, metadata: { deleted_by_admin: adminId } };
  }

  // -------------------------
  // COPY RELATIONS
  // -------------------------
  async listRelations(query = {}) {
    const page = Math.max(parseInt(query.page || "1", 10), 1);
    const limit = Math.min(Math.max(parseInt(query.limit || "20", 10), 1), 100);
    const offset = (page - 1) * limit;

    const where = {};
    if (query.status) where.status = query.status;
    if (query.type) where.type = query.type;
    if (query.copierId) where.copierId = query.copierId;
    if (query.traderId) where.traderId = query.traderId;
    if (query.traderProfileId) where.traderProfileId = query.traderProfileId;

    const { rows, count } = await CopyRelation.findAndCountAll({
      where,
      limit,
      offset,
      order: [["created_at", "DESC"]],
      include: [
        { model: TraderProfile, as: "traderProfile", required: true },
        {model: User, as: "copier", required: true, attributes: ["id", "email"] },
      ],
    });
    const traders = await TraderProfile.findAll({

    })

    return { count, page, limit, relations: rows, traders: traders };
  }

  async getRelationById(id) {
    const relation = await CopyRelation.findByPk(id, {
      include: [{ model: TraderProfile, as: "traderProfile", required: false }],
    });
    if (!relation) throw new NotFoundError("Copy relation not found");
    return { relation };
  }

  async createRelation(data, adminId = null) {
    const copierId = toNum(data.copierId);
    const traderProfileId = toNum(data.traderProfileId);
    const type = String(data.type || "").toLowerCase();

    if (!copierId) throw new ValidationError("copierId is required");
    if (!traderProfileId) throw new ValidationError("traderProfileId is required");
    if (!["follow", "copy"].includes(type)) throw new ValidationError("type must be follow or copy");

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

    const traderProfile = await TraderProfile.findByPk(traderProfileId);
    if (!traderProfile) throw new NotFoundError("Trader profile not found");

    if (type === "copy" && traderProfile.acceptingCopiers === false) {
      throw new BadRequestError("Trader is not accepting copiers");
    }

    // enforce unique (copierId, traderProfileId, type) at app level too
    const exists = await CopyRelation.findOne({
      where: { copierId, traderProfileId, type, status: { [Op.ne]: "stopped" } },
    });
    if (exists) throw new ConflictError("Relation already exists");

    const payload = {
      copierId,
      traderId: traderProfileId, // optional
      traderProfileId,
      type,
      status: data.status || "active",
      copyAmount: data.copyAmount !== undefined ? toNum(data.copyAmount) : null,
      copyMode: data.copyMode || "proportional",
      maxPerTrade: data.maxPerTrade !== undefined ? toNum(data.maxPerTrade) : null,
      stopLossPercent: data.stopLossPercent !== undefined ? toNum(data.stopLossPercent) : null,
      takeProfitPercent: data.takeProfitPercent !== undefined ? toNum(data.takeProfitPercent) : null,
      notifications: data.notifications === undefined ? true : !!data.notifications,
      invested: data.invested !== undefined ? toNum(data.invested, 0) : (type === "copy" ? toNum(data.copyAmount, 0) : 0),
      currentValue: data.currentValue !== undefined ? toNum(data.currentValue, 0) : (type === "copy" ? toNum(data.copyAmount, 0) : 0),
      totalProfit: toNum(data.totalProfit, 0),
      profitPercent: toNum(data.profitPercent, 0),
      copiedTrades: toNum(data.copiedTrades, 0),
      winningTrades: toNum(data.winningTrades, 0),
      losingTrades: toNum(data.losingTrades, 0),
      startedAt: data.startedAt ? new Date(data.startedAt) : new Date(),
      metadata: data.metadata && typeof data.metadata === "object" ? data.metadata : {},
    };

    return await sequelize.transaction(async (t) => {
      const relation = await CopyRelation.create(payload, { transaction: t });

      // update trader profile counters
      if (type === "follow") {
        await traderProfile.update(
          { totalFollowers: Number(traderProfile.totalFollowers || 0) + 1 },
          { transaction: t }
        );
      } else if (type === "copy") {
        await traderProfile.update(
          { totalCopiers: Number(traderProfile.totalCopiers || 0) + 1 },
          { transaction: t }
        );
      }

      // audit
      await relation.update(
        { metadata: { ...(relation.metadata || {}), created_by_admin: adminId } },
        { transaction: t }
      );

      return relation;
    });
  }

  async updateRelation(id, data, adminId = null) {
    const relation = await CopyRelation.findByPk(id);
    if (!relation) throw new NotFoundError("Copy relation not found");

    const patch = { ...data };

    // normalize numerics
    const numericFields = [
      "copyAmount",
      "maxPerTrade",
      "stopLossPercent",
      "takeProfitPercent",
      "invested",
      "currentValue",
      "totalProfit",
      "profitPercent",
      "copiedTrades",
      "winningTrades",
      "losingTrades",
    ];
    for (const f of numericFields) {
      if (patch[f] !== undefined) patch[f] = toNum(patch[f], relation[f]);
    }

    if (patch.startedAt !== undefined && patch.startedAt) patch.startedAt = new Date(patch.startedAt);
    if (patch.pausedAt !== undefined && patch.pausedAt) patch.pausedAt = new Date(patch.pausedAt);
    if (patch.stoppedAt !== undefined && patch.stoppedAt) patch.stoppedAt = new Date(patch.stoppedAt);

    // metadata audit
    if (patch.metadata !== undefined) {
      patch.metadata =
        patch.metadata && typeof patch.metadata === "object"
          ? patch.metadata
          : relation.metadata || {};
      patch.metadata = { ...patch.metadata, updated_by_admin: adminId };
    } else {
      patch.metadata = { ...(relation.metadata || {}), updated_by_admin: adminId };
    }

    await relation.update(patch);
    return relation;
  }

  async setRelationStatus(id, status, adminId = null) {
    const relation = await CopyRelation.findByPk(id);
    if (!relation) throw new NotFoundError("Copy relation not found");

    if (!["active", "paused", "stopped"].includes(status)) {
      throw new ValidationError("Invalid status");
    }

    const traderProfile = await TraderProfile.findByPk(relation.traderProfileId);
    if (!traderProfile) throw new NotFoundError("Trader profile not found");

    return await sequelize.transaction(async (t) => {
      const prev = relation.status;

      const patch = { status, metadata: { ...(relation.metadata || {}), updated_by_admin: adminId } };

      if (status === "paused") patch.pausedAt = new Date();
      if (status === "stopped") patch.stoppedAt = new Date();

      await relation.update(patch, { transaction: t });

      // If transitioning to stopped from non-stopped, decrement counters
      if (prev !== "stopped" && status === "stopped") {
        if (relation.type === "follow") {
          await traderProfile.update(
            { totalFollowers: Math.max(0, Number(traderProfile.totalFollowers || 0) - 1) },
            { transaction: t }
          );
        } else if (relation.type === "copy") {
          await traderProfile.update(
            { totalCopiers: Math.max(0, Number(traderProfile.totalCopiers || 0) - 1) },
            { transaction: t }
          );
        }
      }

      return relation;
    });
  }

  async deleteRelation(id, adminId = null) {
    const relation = await CopyRelation.findByPk(id);
    if (!relation) throw new NotFoundError("Copy relation not found");

    const traderProfile = await TraderProfile.findByPk(relation.traderProfileId);
    if (!traderProfile) throw new NotFoundError("Trader profile not found");

    return await sequelize.transaction(async (t) => {
      // decrement counters if not already stopped
      if (relation.status !== "stopped") {
        if (relation.type === "follow") {
          await traderProfile.update(
            { totalFollowers: Math.max(0, Number(traderProfile.totalFollowers || 0) - 1) },
            { transaction: t }
          );
        } else if (relation.type === "copy") {
          await traderProfile.update(
            { totalCopiers: Math.max(0, Number(traderProfile.totalCopiers || 0) - 1) },
            { transaction: t }
          );
        }
      }

      await relation.destroy({ transaction: t });
      return { deleted: true, id, metadata: { deleted_by_admin: adminId } };
    });
  }

  // -------------------------
  // COPY Trades
  // -------------------------

  async listTrades(query = {}) {
    const page = Math.max(parseInt(query.page || "1", 10), 1);
    const limit = Math.min(Math.max(parseInt(query.limit || "20", 10), 1), 100);
    const offset = (page - 1) * limit;

    const where = {};
    if (query.status) where.status = query.status;
    if (query.type) where.type = query.type;
    if (query.traderId) where.traderId = query.traderId;
    if (query.traderProfileId) where.traderProfileId = query.traderProfileId;
    if (query.symbol) where.symbol = query.symbol;

    const { rows, count } = await CopyTrade.findAndCountAll({
      where,
      limit,
      offset,
      order: [["created_at", "DESC"]],
      include: [
        { model: TraderProfile, as: "trader", required: true },
          { model: MarketPair, as: "pair", required: true , attributes: ["id", "symbol"], include: [{ model: Asset, as: "base", required: true, attributes: ["id", "symbol", "name"] }] },
      ],
    });

    const marketPair = await MarketPair.findAll({
      include: [{ model: Asset, as: "base", required: true, attributes: ["id", "symbol", "name"] }]
    })

    const traders = await TraderProfile.findAll({

    })

    return {
      count,
      page,
      limit,
      trades: rows,
      pairs: marketPair,
      traders
    };
  }

  async getTradeById(id) {
    const trade = await CopyTrade.findByPk(id, {
      include: [
        { model: TraderProfile, as: "traderProfile", required: false },
        { model: User, as: "copier", required: false },
      ],
    });

    if (!trade) throw new NotFoundError("Trade not found");

    return { trade };
  }

  async createTrade(data, adminId = null) {
    const traderProfileId = toNum(data.trader_id);
    const copierId = toNum(data.copierId);
    if (!traderProfileId) throw new ValidationError("traderProfileId is required");
    //if (!copierId) throw new ValidationError("copierId is required");

    const traderProfile = await TraderProfile.findOne({ where: { id: traderProfileId } });
    if (!traderProfile) throw new NotFoundError("Trader profile not found");

    /* const copier = await User.findByPk(copierId);
    if (!copier) throw new NotFoundError("Copier not found"); */

    const payload = {
      traderId: toNum(data.trader_id),
      //traderProfileId: toNum(data.traderProfileId), // optional but useful

      pairId: toNum(data.pair_id),

      type: data.type,
      side: data.side,

      marketType: data.marketType || "market",
      marketLimit: data.marketLimit || null,

      amount: data.amount !== undefined ? toNum(data.amount) : null,
      leverage: data.leverage !== undefined ? toNum(data.leverage) : null,

      payoutPercent: data.payoutPercent !== undefined ? toNum(data.payoutPercent) : null,

      entryPrice: data.entryPrice !== undefined ? toNum(data.entryPrice) : null,
      closingPrice: data.closingPrice !== undefined ? toNum(data.closingPrice) : null,

      duration: data.duration !== undefined ? toNum(data.duration) : null,

      stopLoss: data.stopLoss !== undefined ? toNum(data.stopLoss) : null,
      takeProfit: data.takeProfit !== undefined ? toNum(data.takeProfit) : null,

      profitAmount: toNum(data.profitAmount, 0),

      status: data.status || "open",

      openedAt: data.openedAt ? new Date(data.openedAt) : new Date(),
      closedAt: data.closedAt ? new Date(data.closedAt) : null,

      metadata: {
        ...(data.metadata || {}),
        created_by_admin: adminId,
      },
    };

    return await sequelize.transaction(async (t) => {
      try {
        const trade = await CopyTrade.create(payload, { transaction: t });
        return trade;
      } catch (e) {
        logger.error("Error during trade creation transaction:", e);
        throw e;
      }

    });
  }

  async updateTrade(id, data, adminId = null) {
    const trade = await CopyTrade.findByPk(id);
    if (!trade) throw new NotFoundError("Trade not found");

    const patch = { ...data };

    const numericFields = ["amount", "price", "quantity", "pnl", "fees"];
    for (const f of numericFields) {
      if (patch[f] !== undefined) patch[f] = toNum(patch[f], trade[f]);
    }

    if (patch.openedAt) patch.openedAt = new Date(patch.openedAt);
    if (patch.closedAt) patch.closedAt = new Date(patch.closedAt);

    patch.metadata = {
      ...(trade.metadata || {}),
      ...(patch.metadata || {}),
      updated_by_admin: adminId,
    };

    await trade.update(patch);
    return trade;
  }

  async deleteTrade(id, adminId = null) {
    const trade = await CopyTrade.findByPk(id);
    if (!trade) throw new NotFoundError("Trade not found");

    await trade.destroy();

    return {
      deleted: true,
      id,
      metadata: { deleted_by_admin: adminId },
    };
  }

  async pauseTrade(id, adminId = null) {
    const trade = await CopyTrade.findByPk(id);
    if (!trade) throw new NotFoundError("Trade not found");

    await trade.update({
      status: "paused",
      metadata: {
        ...(trade.metadata || {}),
        updated_by_admin: adminId,
      },
    });

    return trade;
  }

  async resumeTrade(id, adminId = null) {
    const trade = await CopyTrade.findByPk(id);
    if (!trade) throw new NotFoundError("Trade not found");

    await trade.update({
      status: "open",
      metadata: {
        ...(trade.metadata || {}),
        updated_by_admin: adminId,
      },
    });

    return trade;
  }

  async stopTrade(id, adminId = null) {
    const trade = await CopyTrade.findByPk(id);
    if (!trade) throw new NotFoundError("Trade not found");

    await trade.update({
      status: "closed",
      closedAt: new Date(),
      metadata: {
        ...(trade.metadata || {}),
        updated_by_admin: adminId,
      },
    });

    return trade;
  }

}

module.exports = new adminCopyTradingService();