const crypto = require('crypto');
const axios = require('axios');
const nodemailer = require('nodemailer');
const { Resend } = require('resend');
const { fiat_currency } = require('../config/currency');
const country = require('../config/country');
const logger = require('./logger');
const storage = require('../config/context');
const config = require('../config');
const { ValidationError } = require('./errors');
//const resend = new Resend(process.env.RESEND_API_KEY);

const baseUrl = storage.get()?.url ?? config.backendUrl;

/**
 * Generate a random string of specified length
 */
const generateRandomString = (length = 32) => {
  return crypto.randomBytes(length).toString('hex').slice(0, length);
};

/**
 * Generate a random numeric code of specified length
 */
const generateNumericCode = (length = 6) => {
  const min = Math.pow(10, length - 1);
  const max = Math.pow(10, length) - 1;
  return Math.floor(Math.random() * (max - min + 1)) + min;
};

/**
 * Generate a referral code
 */
const generateReferralCode = (prefix = '') => {
  const code = generateRandomString(8).toUpperCase();
  return prefix ? `${prefix}${code}` : code;
};


/**
 * Generate an API key
 */
const generateApiKey = () => {
  return `pk_${generateRandomString(32)}`;
};

/**
 * Generate an API secret
 */
const generateApiSecret = () => {
  return `sk_${generateRandomString(48)}`;
};

/**
 * Generate a transaction ID
 */
const generateTransactionId = (prefix = 'TXN') => {
  const timestamp = Date.now().toString(36).toUpperCase();
  const random = generateRandomString(6).toUpperCase();
  return `${prefix}-${timestamp}-${random}`;
};

/**
 * Generate an order ID
 */
const generateOrderId = (type = 'ORD') => {
  const timestamp = Date.now().toString(36).toUpperCase();
  const random = generateRandomString(4).toUpperCase();
  return `${type}-${timestamp}-${random}`;
};

/**
 * Generate a ticket ID
 */
const generateTicketId = () => {
  const year = new Date().getFullYear();
  const random = Math.floor(Math.random() * 1000000).toString().padStart(6, '0');
  return `TKT-${year}-${random}`;
};

/**
 * Mask an email address
 */
const maskEmail = (email) => {
  const [name, domain] = email.split('@');
  const maskedName = name.charAt(0) + '*'.repeat(Math.max(name.length - 2, 1)) + name.charAt(name.length - 1);
  return `${maskedName}@${domain}`;
};

/**
 * Mask a phone number
 */
const maskPhone = (phone) => {
  if (!phone || phone.length < 4) return phone;
  return phone.slice(0, 3) + '*'.repeat(phone.length - 6) + phone.slice(-3);
};

/**
 * Mask a wallet address
 */
const maskWalletAddress = (address) => {
  if (!address || address.length < 10) return address;
  return `${address.slice(0, 6)}...${address.slice(-4)}`;
};

/**
 * Format currency
 */
const formatCurrency = (amount, currency = 'USD', locale = 'en-US') => {
  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency,
  }).format(amount);
};

/**
 * Format percentage
 */
const formatPercentage = (value, decimals = 2) => {
  return `${(value * 100).toFixed(decimals)}%`;
};

/**
 * Calculate percentage change
 */
const calculatePercentageChange = (oldValue, newValue) => {
  if (oldValue === 0) return 0;
  return ((newValue - oldValue) / oldValue) * 100;
};

/**
 * Round to specified decimal places
 */
const roundToDecimals = (value, decimals = 2) => {
  return Math.round(value * Math.pow(10, decimals)) / Math.pow(10, decimals);
};

/**
 * Check if value is within range
 */
const isInRange = (value, min, max) => {
  return value >= min && value <= max;
};

/**
 * Paginate array
 */
const paginate = (array, page = 1, limit = 10) => {
  const startIndex = (page - 1) * limit;
  const endIndex = startIndex + limit;

  return {
    data: array.slice(startIndex, endIndex),
    pagination: {
      currentPage: page,
      totalPages: Math.ceil(array.length / limit),
      totalItems: array.length,
      itemsPerPage: limit,
      hasNextPage: endIndex < array.length,
      hasPrevPage: page > 1,
    },
  };
};

/**
 * Sleep for specified milliseconds
 */
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));

/**
 * Retry function with exponential backoff
 */
const retry = async (fn, maxRetries = 3, delay = 1000) => {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await sleep(delay * Math.pow(2, i));
    }
  }
};

/**
 * Parse sort query string
 */
const parseSortQuery = (sortString, allowedFields = []) => {
  if (!sortString) return {};

  const sortObj = {};
  const fields = sortString.split(',');

  fields.forEach(field => {
    const order = field.startsWith('-') ? -1 : 1;
    const fieldName = field.replace(/^-/, '');

    if (allowedFields.length === 0 || allowedFields.includes(fieldName)) {
      sortObj[fieldName] = order;
    }
  });

  return sortObj;
};

/**
 * Build MongoDB filter from query params
 */
const buildFilter = (query, allowedFilters = {}) => {
  const filter = {};

  Object.keys(allowedFilters).forEach(key => {
    if (query[key] !== undefined) {
      const { field, type } = allowedFilters[key];
      const value = query[key];

      switch (type) {
        case 'exact':
          filter[field || key] = value;
          break;
        case 'regex':
          filter[field || key] = { $regex: value, $options: 'i' };
          break;
        case 'number':
          filter[field || key] = Number(value);
          break;
        case 'boolean':
          filter[field || key] = value === 'true';
          break;
        case 'array':
          filter[field || key] = { $in: value.split(',') };
          break;
        case 'dateRange':
          if (query[`${key}From`]) {
            filter[field || key] = { $gte: new Date(query[`${key}From`]) };
          }
          if (query[`${key}To`]) {
            filter[field || key] = {
              ...filter[field || key],
              $lte: new Date(query[`${key}To`])
            };
          }
          break;
        default:
          filter[field || key] = value;
      }
    }
  });

  return filter;
};

const randomNumber = function (length = 3) {
  const min = Math.pow(10, length - 1);
  const max = Math.pow(10, length) - 1;
  return Math.floor(Math.random() * (max - min + 1)) + min;
}


const generateUniqueUsernameRandom = function (email, maxAttempts = 10) {
  let base = email
    .split("@")[0]
    .toLowerCase()
    .replace(/[^a-z0-9]/g, "");

  let username = base;

  const suffix = randomNumber(6); // e.g. 482
  const trimmedBase = base.slice(0, 20 - suffix.toString().length);
  username = `${trimmedBase}${suffix}`;

  return username
}

const computeBotEndDate = (startDate, pricePeriod) => {
  if (!startDate) throw new Error("startDate is required");

  const start = new Date(startDate);
  const end = new Date(start.getTime());

  switch (pricePeriod) {
    case "day":
      end.setUTCDate(end.getUTCDate() + 1);
      break;

    case "week":
      end.setUTCDate(end.getUTCDate() + 7);
      break;

    case "month": {
      const day = end.getUTCDate();
      end.setUTCMonth(end.getUTCMonth() + 1);

      // Fix month overflow (e.g. Jan 31 → Feb 28/29)
      if (end.getUTCDate() < day) {
        end.setUTCDate(0); // last day of previous month
      }
      break;
    }

    case "year": {
      const month = end.getUTCMonth();
      end.setUTCFullYear(end.getUTCFullYear() + 1);

      // Handle Feb 29 → Feb 28
      if (end.getUTCMonth() !== month) {
        end.setUTCDate(0);
      }
      break;
    }

    case "lifetime":
      return null; // IMPORTANT: no expiry

    default:
      // fallback: 30 days
      end.setUTCDate(end.getUTCDate() + 30);
      break;
  }

  return end;
};


function addDuration(date, duration = 0, period = "days") {
  const d = new Date(date);

  const n = Number(duration);
  if (!Number.isFinite(n) || n <= 0) return d;

  switch (String(period).toLowerCase()) {
    case "minute":
    case "minutes":
      d.setMinutes(d.getMinutes() + n);
      return d;

    case "hour":
    case "hours":
      d.setHours(d.getHours() + n);
      return d;

    case "day":
    case "days":
      d.setDate(d.getDate() + n);
      return d;

    case "week":
    case "weeks":
      d.setDate(d.getDate() + n * 7);
      return d;

    case "month":
    case "months":
      d.setMonth(d.getMonth() + n);
      return d;

    case "year":
    case "years":
      d.setFullYear(d.getFullYear() + n);
      return d;

    default:
      // fallback: treat unknown as days
      d.setDate(d.getDate() + n);
      return d;
  }
}

function toISO(date) {
  // safe for API responses
  return date ? new Date(date).toISOString() : null;
}

const safeNumber = (v) => {
  const n = Number(v);
  return Number.isFinite(n) ? n : 0;
};


const transporter = nodemailer.createTransport({
  host: "invsta.website",
  port: 587,
  secure: false, // Use true for port 465, false for port 587
  auth: {
    user: "no-reply@invsta.website",
    pass: "Demo0504@",
  },
});

/* const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST,
  port: Number(process.env.SMTP_PORT || 465),
  secure: true, // 465
  auth: {
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASS,
  },

  // ✅ prevents hanging/timeouts on Render
  connectionTimeout: 10_000,
  greetingTimeout: 10_000,
  socketTimeout: 20_000,

  // ✅ helps when sending many emails
  pool: true,
  maxConnections: 2,
  maxMessages: 50,
}); */



const sendOtpEmail = async ({ email, code, logger }) => {
  try {
    const res = await resend.emails.send({
      from: process.env.MAIL_FROM, // e.g. "Your App <no-reply@yourdomain.com>"
      to: [email],
      subject: "Verification Code",
      html: `<h2>Your code is ${code}</h2>`,
      text: `Your code is ${code}`,
    });
    logger?.info?.(`Resend OTP sent: ${email}`);
    logger?.info?.(`Resend Details sent: ${res}`);
    return true;
  } catch (err) {

    const status = err?.statusCode || err?.status || err?.response?.status;
    const msg = err?.message || "";
    if (status === 429 || /rate limit|quota|limit/i.test(msg)) {
      logger?.warn?.({ status, err }, `Resend quota/rate limit hit (OTP not sent): ${email}`);
    } else {
      logger?.error?.({ status, err }, `Resend OTP send failed: ${email}`);
    }
    return false;
  }
}

const mapAsset = (a) => {
  let rand1 = (Math.random() * 1000).toFixed(2);
  let rand2 = (Math.random() * 1000).toFixed(2);
  const currentPrice = Number(a.rate?.price ?? rand1);
  const previousClose = Number(a.rate?.previous ?? rand2);
  const priceChange = currentPrice - previousClose;
  const priceChangePercent = ((priceChange / previousClose) * 100).toFixed(2);
  const high52w = Number(a.rate?.high52w ?? Math.max(rand1, rand2));
  const low52w = Number(a.rate?.low52w ?? Math.min(rand1, rand2));
  const dayHigh = Number(a.rate?.dayHigh ?? Math.max(rand1, rand2));
  const dayLow = Number(a.rate?.dayLow ?? Math.min(rand1, rand2));
  const volume = a.rate?.volume ?? 0;
  const avgVolume = a.rate?.avgVolume ?? 0;
  const marketCap = a.rate?.marketCap ?? 0;
  const peRatio = a.rate?.peRatio ?? 0;
  const eps = a.rate?.eps ?? 0;
  const dividend = a.rate?.dividend ?? 0;
  const dividendYield = a.rate?.dividendYield ?? 0;
  const beta = a.rate?.beta ?? 0;
  return {
    symbol: a.symbol ?? '',
    name: a.name ?? '',
    price: currentPrice,
    previousClose,
    change: priceChange,
    changePercent: priceChangePercent,
    high52w,
    low52w,
    dayHigh,
    dayLow,
    volume,
    avgVolume,
    marketCap,
    peRatio,
    eps,
    dividend,
    dividendYield,
    beta,
    type: a.type,
    exchange: a.exchange,
    logo: a.logo,

  }
}


const mapPair = (a) => {
  let rand1 = (Math.random() * 1000).toFixed(2);
  let rand2 = (Math.random() * 1000).toFixed(2);
  const logo = `${baseUrl}/uploads/assets/${a.base?.logo}`
  const stats = a.base?.stats ?? {}

  return {
    symbol: a.symbol,
    base: a.base?.symbol,
    name: a.base?.name,
    quote: a.quote?.symbol,
    quote_name: a.base?.name,
    price: a.rate?.current_price,
    high24h: a.rate?.high24h,
    low24h: a.rate?.low24h,
    volume24h: a.rate?.volume24h,
    logo: logo,
    decimals: a.decimals,
    precision: a.pricePrecision,
    industry: a.base?.industry,
    exchange: a.base?.exchange,
    stats: {
      marketCap: stats?.marketCap ?? 0,
      volume: stats?.volume24h ?? 0,
      peRatio: stats?.peRatio ?? 0,
      pegRatio: stats?.pegRatio ?? 0,
      ebitda: stats?.ebitda ?? 0,
      eps: stats?.eps ?? 0,
      dividendPerShare: stats?.dividendPerShare ?? 0,
      dividendYield: stats?.dividendYield ?? 0,
      beta: stats?.beta ?? 0,
      revenue: stats?.revenue ?? 0,
      revenuePerShare: stats?.revenuePerShare ?? 0,
      operatingMargin: stats?.operatingMargin ?? 0,
      grossProfit: stats?.grossProfit ?? 0,
      profitMargin: stats?.profitMargin ?? 0,
      returnOnAssets: stats?.returnOnAssets ?? 0,
      returnOnEquity: stats?.returnOnEquity ?? 0,
      high52w: stats?.high52w ?? 0,
      low52w: stats?.low52w ?? 0,
    }
  }
}


const mapWatchlist = (l) => {
  const price = Number(l.pair?.rate?.current_price ?? 0)
  const change24h = Number(l.pair?.rate?.change24h ?? 0)
  const change7d = Number(l.pair?.rate?.change7d ?? 0)
  const marketCap = Number(l.pair?.rate?.marketCap ?? 0)
  const volume = Number(l.pair?.rate?.volume ?? 0)

  return {
    id: l.id,
    symbol: l.pair?.symbol,
    name: l.pair?.name,
    price: price ?? 0,
    change24h: change24h,
    change7d: change7d,
    marketCap: marketCap,
    volume: volume,
    logo: getUploadedFile(l.pair?.base?.logo, "assets"),
    color: '#f7931a',
    sparkline: [42, 42.5, 43, 42.8, 43.2, 43.5, 43.6],
    type: l.pair?.type,
    alerts: [{}],
    addedAt: l.createdAt
  }
}
const mapDepositMethod = (m) => {
  const minimum = Number(m.minimum ?? 0)
  const maximum = Number(m.maximum ?? 0)
  const logo = `${baseUrl}/uploads/deposit-img/${m.logo}`
  return {
    id: m.reference,
    slug: m.slug,
    name: m.name,
    symbol: m.symbol,
    logo: logo,
    type: m.type,
    auto: m.auto,
    digit: m.digit,
    details: m.details,
    exchange: m.exchange,
    minimum: minimum,
    maximum: maximum,
    qr: m.qrCode,
    meta: m.config
  }
}

const mapWithdrawalMethod = (m) => {
  const minimum = Number(m.minimum ?? 0)
  const maximum = Number(m.maximum ?? 0)
  return {
    id: m.reference,
    name: m.name,
    slug: m.slug,
    symbol: m.symbol,
    logo: m.logo,
    type: m.type,
    minimum: minimum,
    maximum: maximum,
    duration: m.duration,

  }
}

const mapUser = (user) => {
  const newUser = user;
  newUser.balance = user.balance?.balance
  newUser.bonus = user.balance?.bonus
  newUser.profit = user.balance?.profit
  const currencies = fiat_currency.find(t => user?.currency === t.code);
  const countries = country.find(t => user?.country === t.code);
  newUser.currency_symbol = currencies?.symbol;
  newUser.currency_name = currencies?.name;
  newUser.country_name = countries?.name
  return newUser
}

const formatNumberShort = (num) => {
  if (num === null || num === undefined) return "0";

  const abs = Math.abs(num);

  if (abs >= 1_000_000_000) {
    return (num / 1_000_000_000).toFixed(1).replace(/\.0$/, "") + "B";
  }

  if (abs >= 1_000_000) {
    return (num / 1_000_000).toFixed(1).replace(/\.0$/, "") + "M";
  }

  if (abs >= 100_000) {
    return (num / 1_000).toFixed(0) + "K";
  }

  if (abs >= 1_000) {
    return (num / 1_000).toFixed(1).replace(/\.0$/, "") + "K";
  }

  return num.toString();
}

const mapPrice = (price) => {
  const symbol = price.coin?.symbol || price.asset?.symbol || 'None';
  if (!symbol) return null
  return {
    symbol: symbol,
    price: price.current_price,
    open: price.open24h,
    high: price.high24h,
    low: price.low24h,
    volume: price.volume24h,
    change24h: price.change24h,
    perc24h: price.change_percent24h
  }
}


const safeNum = (v) => {
  const n = Number(v);
  return Number.isFinite(n) ? n : 0;
};

const getLotTime = (lot) => {
  return lot?.timestamp || lot?.createdAt || lot?.created_at || null;
};

const buildClosedLotPriceSeries = (lots = []) => {
  return (Array.isArray(lots) ? lots : [])
    .filter((lot) => lot?.status === "closed")
    .map((lot) => ({
      timestamp: getLotTime(lot),
      price: safeNum(lot?.buyPrice),
    }))
    .filter((point) => point.timestamp)
    .sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
}

const buildClosedHoldingChangeSeries = (lots = []) => {
  const closedLots = (Array.isArray(lots) ? lots : [])
    .filter((lot) => lot?.status === "closed")
    .sort((a, b) => new Date(getLotTime(a)) - new Date(getLotTime(b)));

  let cumulativeValue = 0;

  return closedLots.map((lot) => {
    const quantity = safeNum(lot?.quantity);
    const price = safeNum(lot?.buyPrice);
    const value = quantity * price;

    const signedValue = lot?.side === "sell" ? -value : value;
    cumulativeValue += signedValue;

    return {
      timestamp: getLotTime(lot),
      price,
      value,
      cumulativeValue,
    };
  });
}

/* const sendEmail = (data) => {
  const { type = "generic", email } = data || {}
  console.log(process.env.EMAIL_SENDER)
  fetch(process.env.EMAIL_SENDER, {
    method: 'POST',
    body: data,
    headers: {
      Authorization: 'Bearer ashieiuejidjkdlk_woiowwiwk.sjjs2729829kAJHjahnm'
    }
  }).then(() => logger.info(`${type} email sent: ${email}`))
    .catch((err) => logger.info({ err }, `${type} email failed: ${email}`));
} */


const sendEmail = async ({ type, to, subject, data = {} }) => {
  try {
    const r = await axios.post(process.env.EMAIL_SENDER, {
      type,
      to,
      data,
      subject
    })
  } catch (err) {
    console.log(err)
  }
}

const getUploadedFile = (logo, folder = null) => {

  return `${baseUrl}/uploads/${folder ? `${folder}/` : ''}${logo}`

}

const parseBoolean = (val) => {
  if (typeof val === "boolean") return val;

  if (typeof val === "string") {
    const v = val.trim().toLowerCase();
    if (["true", "1", "yes", "on"].includes(v)) return true;
    if (["false", "0", "no", "off"].includes(v)) return false;
  }

  if (typeof val === "number") return val === 1;

  throw new ValidationError(`Invalid boolean value: ${val}`);
};

const notifyUser = async (userId, body = {}, adminId = null) => {

};

const hasRequiredKyc = (userKycLevel, requiredLevel = 'level1') => {
  const levels = ['none', 'pending', 'level1', 'level2', 'level3', 'level4'];

  return levels.indexOf(userKycLevel) >= levels.indexOf(requiredLevel);
};

module.exports = {
  baseUrl,
  generateRandomString,
  generateNumericCode,
  generateReferralCode,
  generateApiKey,
  generateApiSecret,
  generateTransactionId,
  generateOrderId,
  generateTicketId,
  maskEmail,
  maskPhone,
  maskWalletAddress,
  formatCurrency,
  formatPercentage,
  calculatePercentageChange,
  roundToDecimals,
  isInRange,
  paginate,
  sleep,
  retry,
  parseSortQuery,
  buildFilter,
  generateUniqueUsernameRandom,
  computeBotEndDate,
  addDuration,
  toISO,
  safeNumber,
  transporter,
  sendOtpEmail,
  mapAsset,
  mapPair,
  mapDepositMethod,
  mapWithdrawalMethod,
  mapUser,
  formatNumberShort,
  mapPrice,
  mapWatchlist,
  safeNum,
  getLotTime,
  buildClosedLotPriceSeries,
  buildClosedHoldingChangeSeries,
  sendEmail,
  getUploadedFile,
  parseBoolean,
  notifyUser,
  hasRequiredKyc
};