"use strict";

const { Signal, SignalProvider, MarketPair, Asset } = require("../../models");
const { Op } = require("sequelize");
const { ValidationError, NotFoundError, BadRequestError } = require("../../utils/errors");

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

const parseJSON = (v) => {
  if (v === null || v === undefined || v === "") return null;

  if (typeof v === "object") return v;

  try {
    return JSON.parse(v);
  } catch (e) {
    return String(v)
      .split(",")
      .map((x) => x.trim())
      .filter(Boolean);
  }
};

const toBool = (v) => {
  if (v === true || v === false) return v;
  if (v === "true") return true;
  if (v === "false") return false;
  return false;
};




const parseTags = (tags) => {
  if (tags === null || tags === undefined || tags === "") return null;

  // already array/object
  if (Array.isArray(tags) || (typeof tags === "object" && tags !== null)) return tags;

  // string: try JSON, else split by comma
  const s = String(tags).trim();
  if (!s) return null;

  try {
    const j = JSON.parse(s);
    return j;
  } catch (e) {
    return s
      .split(",")
      .map((x) => x.trim())
      .filter(Boolean);
  }
};

const buildProviderPayload = (data, isUpdate = false) => {
  const payload = {};

  // required only on create
  if (!isUpdate || data.provider !== undefined) {
    if (!data.provider && !isUpdate) throw new ValidationError("provider is required");
    payload.provider = data.provider;
  }

  if (!isUpdate || data.name !== undefined) {
    if (!data.name && !isUpdate) throw new ValidationError("name is required");
    payload.name = data.name;
  }

  // simple fields
  const fields = [
    "description",
    "avatar",
    "tradingStyle",
    "experience",
    "telegramChannel",
    "discordServer",
    "website",
  ];

  for (const f of fields) {
    if (data[f] !== undefined) {
      payload[f] = data[f] || null;
    }
  }

  // enums
  if (data.tier !== undefined) {
    if (!["basic", "premium", "elite"].includes(data.tier)) {
      throw new ValidationError("Invalid tier");
    }
    payload.tier = data.tier;
  }

  if (data.pricePeriod !== undefined) {
    if (!["month", "year", "lifetime"].includes(data.pricePeriod)) {
      throw new ValidationError("Invalid pricePeriod");
    }
    payload.pricePeriod = data.pricePeriod;
  }

  if (data.status !== undefined) {
    if (!["active", "inactive", "suspended"].includes(data.status)) {
      throw new ValidationError("Invalid status");
    }
    payload.status = data.status;
  }

  if (data.ratings !== undefined) payload.rating = toNum(data.ratings) ?? 0;
  if (data.total !== undefined) payload.totalSignals = toNum(data.total) ?? 0;
  if (data.won !== undefined) payload.successfulSignals = toNum(data.won) ?? 0;
  if (data.totalProfit !== undefined) payload.totalProfit = toNum(data.totalProfit) ?? 0;
  if (data.subscriber !== undefined) payload.subscribers = toNum(data.subscriber) ?? 0;
  if (data.avgProfit !== undefined) payload.avgProfitPercent = toNum(data.avgProfit) ?? 0;
  if (data.total > 0) {
    payload.winRate = Number(
      ((data.won / data.total) * 100).toFixed(2)
    );
  }
  // numbers
  if (data.price !== undefined) payload.price = toNum(data.price) ?? 0;

  // booleans
  if (data.verified !== undefined) payload.verified = toBool(data.verified);
  if (data.isPublic !== undefined) payload.isPublic = toBool(data.isPublic);
  if (data.isFeatured !== undefined) payload.isFeatured = toBool(data.isFeatured);

  // JSON fields
  if (data.specialties !== undefined) payload.specialties = parseJSON(data.specialties);
  if (data.supportedPairs !== undefined) payload.supportedPairs = parseJSON(data.supportedPairs);

  return payload;
};


class adminSignalService {
  async getSignals(query = {}) {
    const {
      q,
      providerId,
      status,
      type,
      marketType,
      marketEntry,
      confidence,
      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), 200);
    const offset = (pageNum - 1) * limitNum;

    const where = {};
    if (providerId) where.providerId = providerId;
    if (status) where.status = status;
    if (type) where.type = type;
    if (marketType) where.marketType = marketType;
    if (marketEntry) where.marketEntry = marketEntry;
    if (confidence) where.confidence = confidence;

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

    const { rows, count } = await Signal.findAndCountAll({
      where,
      order: [["created_at", "DESC"]],
      include: [{ model: SignalProvider, as: "provider", required: true },{ model: MarketPair, as: "pairs", required: true }],
      limit: limitNum,
      offset,
    });
    const marketPair = await MarketPair.findAll({
      include: [{ model: Asset, as: "base", required: true, attributes: ["id", "symbol", "name"] }]
    })
    const signalProviders = await SignalProvider.findAll({});
    return {
      count,
      page: pageNum,
      limit: limitNum,
      signals: rows,
      pairs: marketPair,
      providers: signalProviders
    };
  }

  async getSignalById(signalId) {
    const signal = await Signal.findByPk(signalId, {
      include: [{ model: SignalProvider, as: "provider", required: false }],
    });
    if (!signal) throw new NotFoundError("Signal not found");
    return { signal };
  }

  async createSignal(data, adminId) {
    if (!data) throw new BadRequestError("Payload is required");


    const provider = await SignalProvider.findOne({ where: { provider: data.providerId } });
    if (!provider) throw new ValidationError("SignalProvider not found");

    const pair = String(data.pair || "").trim();
    if (!pair) throw new ValidationError("pair is required");
    const mPair = await MarketPair.findOne({ where: { symbol: pair } });
    if (!mPair) throw new ValidationError("MarketPair not found for given pair symbol");

    const marketType = data.marketType || "forex";
    const type = data.type;
    const marketEntry = data.marketEntry || "market";

    if (!["spot", "forex", "stock", "terminal"].includes(marketType)) {
      throw new ValidationError("Invalid marketType");
    }
    if (!["buy", "sell", "long", "short", "close"].includes(type)) {
      throw new ValidationError("Invalid type");
    }
    if (!["market", "limit", "stop"].includes(marketEntry)) {
      throw new ValidationError("Invalid marketEntry");
    }

    const entryPrice = toNum(data.entryPrice);
    if (entryPrice === null) throw new ValidationError("entryPrice is required and must be a number");

    const payload = {
      providerId: provider.id,
      pair: mPair.id,
      marketType,
      type,
      marketEntry,

      status: data.status || "active",

      entryPrice,
      currentPrice: toNum(data.currentPrice),
      exitPrice: toNum(data.exitPrice),

      stopLoss: toNum(data.stopLoss),
      takeProfit1: toNum(data.takeProfit1),
      takeProfit2: toNum(data.takeProfit2),
      takeProfit3: toNum(data.takeProfit3),

      leverage: toNum(data.leverage) ?? 1,
      timeframe: data.timeframe || null,

      profitLoss: toNum(data.profitLoss) ?? 0,
      profitLossPercent: toNum(data.profitLossPercent) ?? 0,
      riskRewardRatio: toNum(data.riskRewardRatio),

      analysis: data.analysis || null,
      imageUrl: data.imageUrl || null,
      tags: parseTags(data.tags),

      confidence: data.confidence || "medium",

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

      subscribersTaken: toNum(data.subscribersTaken) ?? 0,
      successfulTrades: toNum(data.successfulTrades) ?? 0,
    };

    // optional: compute R:R if missing and SL/TP1 exist
    if (
      payload.riskRewardRatio === null &&
      payload.stopLoss !== null &&
      payload.takeProfit1 !== null &&
      payload.entryPrice !== null
    ) {
      const risk = Math.abs(payload.entryPrice - payload.stopLoss);
      const reward = Math.abs(payload.takeProfit1 - payload.entryPrice);
      if (risk > 0) payload.riskRewardRatio = Number((reward / risk).toFixed(2));
    }

    const signal = await Signal.create(payload);
    return signal;
  }

  async updateSignal(signalId, data, adminId) {
    const signal = await Signal.findByPk(signalId);
    if (!signal) throw new NotFoundError("Signal not found");

    // providerId (optional)
    if (data.providerId !== undefined) {
      const provider = await SignalProvider.findOne({ where: { provider: data.providerId } });
      if (!provider) throw new ValidationError("SignalProvider not found");
      data.providerId = provider.id;
    }

    const patch = { ...data };

    // normalize numeric fields if present
    const numFields = [
      "entryPrice",
      "currentPrice",
      "exitPrice",
      "stopLoss",
      "takeProfit1",
      "takeProfit2",
      "takeProfit3",
      "leverage",
      "profitLoss",
      "profitLossPercent",
      "riskRewardRatio",
      "subscribersTaken",
      "successfulTrades",
    ];

    for (const k of numFields) {
      if (patch[k] !== undefined) patch[k] = toNum(patch[k]);
    }

    if (patch.tags !== undefined) patch.tags = parseTags(patch.tags);

    if (patch.closedAt !== undefined) patch.closedAt = patch.closedAt ? new Date(patch.closedAt) : null;
    if (patch.expiresAt !== undefined) patch.expiresAt = patch.expiresAt ? new Date(patch.expiresAt) : null;

    if (patch.pair !== undefined) patch.pair = String(patch.pair || "").trim();

    // validate enums if provided
    if (patch.marketType !== undefined && !["spot", "forex", "stock", "terminal"].includes(patch.marketType)) {
      throw new ValidationError("Invalid marketType");
    }
    if (patch.type !== undefined && !["buy", "sell", "long", "short", "close"].includes(patch.type)) {
      throw new ValidationError("Invalid type");
    }
    if (patch.marketEntry !== undefined && !["market", "limit", "stop"].includes(patch.marketEntry)) {
      throw new ValidationError("Invalid marketEntry");
    }
    if (patch.status !== undefined && !["active", "closed", "cancelled", "expired"].includes(patch.status)) {
      throw new ValidationError("Invalid status");
    }
    if (patch.confidence !== undefined && !["low", "medium", "high"].includes(patch.confidence)) {
      throw new ValidationError("Invalid confidence");
    }

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

  async deleteSignal(signalId, adminId) {
    const signal = await Signal.findByPk(signalId);
    if (!signal) throw new NotFoundError("Signal not found");
    await signal.destroy();
    return true;
  }

  async getProviders(query = {}) {
    const { q, status, tier, verified, page = 1, limit = 20 } = query;

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

    const where = {};

    if (status) where.status = status;
    if (tier) where.tier = tier;
    if (verified !== undefined) where.verified = verified;

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

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

    return {
      count,
      page: pageNum,
      limit: limitNum,
      providers: rows,
    };
  }

  async getProviderById(providerId) {
    const provider = await SignalProvider.findByPk(providerId);
    if (!provider) throw new NotFoundError("Provider not found");

    return { provider };
  }

  async createProvider(data, file) {
    if (!data) throw new BadRequestError("Payload required");

    const payload = buildProviderPayload(data, false);
    if (file) payload.avatar = file;
    const provider = await SignalProvider.create(payload);
    return provider;
  }


  async updateProvider(providerId, data, file) {
    const provider = await SignalProvider.findByPk(providerId);
    if (!provider) throw new NotFoundError("Provider not found");

    const payload = buildProviderPayload(data, true);
    if (file) payload.avatar = file;

    await provider.update(payload);
    return provider;
  }


  async deleteProvider(providerId) {
    const provider = await SignalProvider.findByPk(providerId);
    if (!provider) throw new NotFoundError("Provider not found");

    await provider.destroy();
    return true;
  }
}

module.exports = new adminSignalService();