// Tempo — Trades data layer (CRUD + stats) on top of Supabase

async function listTrades({ from, to, symbol } = {}) {
  if (!window.sb) return [];
  let q = window.sb.from('trades').select('*').order('executed_at', { ascending: false });
  if (from)   q = q.gte('executed_at', from);
  if (to)     q = q.lte('executed_at', to);
  if (symbol) q = q.eq('symbol', symbol);
  const { data, error } = await q;
  if (error) { console.error('[Tempo] listTrades', error); return []; }
  return data || [];
}

async function getTrade(id) {
  if (!window.sb) return null;
  const { data, error } = await window.sb.from('trades').select('*').eq('id', id).maybeSingle();
  if (error) { console.error('[Tempo] getTrade', error); return null; }
  return data;
}

async function addTrade(payload) {
  if (!window.sb) return { error: { message: 'Supabase non disponible.' } };
  const { data: userData } = await window.sb.auth.getUser();
  const user_id = userData?.user?.id;
  if (!user_id) return { error: { message: 'Tu dois être connecté.' } };
  const row = sanitizeTradePayload(payload, user_id);
  const { data, error } = await window.sb.from('trades').insert(row).select().single();
  if (error) console.error('[Tempo] addTrade', error);
  return { data, error };
}

async function updateTrade(id, payload) {
  if (!window.sb) return { error: { message: 'Supabase non disponible.' } };
  const patch = { ...payload };
  delete patch.id; delete patch.user_id; delete patch.created_at;
  const { data, error } = await window.sb.from('trades').update(patch).eq('id', id).select().single();
  if (error) console.error('[Tempo] updateTrade', error);
  return { data, error };
}

async function deleteTrade(id) {
  if (!window.sb) return { error: { message: 'Supabase non disponible.' } };
  const { error } = await window.sb.from('trades').delete().eq('id', id);
  if (error) console.error('[Tempo] deleteTrade', error);
  return { error };
}

// Bulk insert with dedupe on (user_id, external_id) when external_id is present.
async function bulkInsertTrades(rows) {
  if (!window.sb) return { error: { message: 'Supabase non disponible.' } };
  const { data: userData } = await window.sb.auth.getUser();
  const user_id = userData?.user?.id;
  if (!user_id) return { error: { message: 'Tu dois être connecté.' } };
  const sanitized = rows.map(r => sanitizeTradePayload(r, user_id));
  if (sanitized.length === 0) return { data: [], error: null };
  // Split: rows with external_id we attempt upsert by (user_id+external_id+imported_from);
  // others just insert.
  const withExt = sanitized.filter(r => r.external_id);
  const noExt   = sanitized.filter(r => !r.external_id);
  let inserted = [];
  if (noExt.length) {
    const { data, error } = await window.sb.from('trades').insert(noExt).select();
    if (error) return { error };
    inserted = inserted.concat(data || []);
  }
  if (withExt.length) {
    // Manual dedupe: fetch existing externals
    const ids = withExt.map(r => r.external_id);
    const { data: existing } = await window.sb.from('trades')
      .select('external_id, imported_from')
      .in('external_id', ids);
    const seen = new Set((existing || []).map(e => `${e.imported_from || ''}::${e.external_id}`));
    const fresh = withExt.filter(r => !seen.has(`${r.imported_from || ''}::${r.external_id}`));
    if (fresh.length) {
      const { data, error } = await window.sb.from('trades').insert(fresh).select();
      if (error) return { error };
      inserted = inserted.concat(data || []);
    }
  }
  return { data: inserted, error: null, skipped: sanitized.length - inserted.length };
}

function sanitizeTradePayload(p, user_id) {
  const dir = String(p.direction || p.dir || '').toLowerCase();
  return {
    user_id,
    symbol: String(p.symbol || '').toUpperCase().trim() || 'UNKNOWN',
    direction: dir === 'short' || dir === 's' || dir === 'sell' ? 'short' : 'long',
    entry: numOrNull(p.entry),
    exit_price: numOrNull(p.exit_price ?? p.exit),
    lots: numOrNull(p.lots) ?? 1,
    pnl: numOrNull(p.pnl) ?? 0,
    r_multiple: numOrNull(p.r_multiple ?? p.r),
    setup: p.setup || null,
    notes: p.notes || null,
    screenshot_url: p.screenshot_url || null,
    executed_at: toISOorNow(p.executed_at || p.date),
    imported_from: p.imported_from || null,
    external_id: p.external_id ? String(p.external_id) : null,
  };
}

function numOrNull(v) {
  if (v === null || v === undefined || v === '') return null;
  const s = String(v).replace(/\s/g, '').replace(',', '.');
  const n = parseFloat(s);
  return Number.isFinite(n) ? n : null;
}

function toISOorNow(v) {
  if (!v) return new Date().toISOString();
  if (v instanceof Date) return v.toISOString();
  // Try ISO first
  const d = new Date(v);
  if (!isNaN(d.getTime())) return d.toISOString();
  return new Date().toISOString();
}

// Compute aggregate stats from a fetched trades array (pure, fast)
function computeStats(trades) {
  if (!Array.isArray(trades) || trades.length === 0) {
    return { netPnl: 0, winRate: 0, profitFactor: 0, avgR: 0, expectancy: 0, count: 0, wins: 0, losses: 0, avgWin: 0, avgLoss: 0 };
  }
  let netPnl = 0, wins = 0, losses = 0, totalWin = 0, totalLoss = 0, sumR = 0, rCount = 0;
  for (const t of trades) {
    const p = Number(t.pnl) || 0;
    netPnl += p;
    if (p > 0) { wins++; totalWin += p; }
    else if (p < 0) { losses++; totalLoss += Math.abs(p); }
    // R-moyen : ne compter QUE les trades réellement notés en R (nombre fini,
    // non vide). Les 0 sont traités comme « non renseignés » (et non comme un
    // break-even saisi), sinon ils écrasent artificiellement la moyenne — c'est
    // exactement le symptôme du R-moyen rabaissé. On exclut null/undefined/''/NaN/0.
    const rv = Number(t.r_multiple);
    if (t.r_multiple !== null && t.r_multiple !== undefined && t.r_multiple !== '' && Number.isFinite(rv) && rv !== 0) {
      sumR += rv; rCount++;
    }
  }
  const count = trades.length;
  const winRate = count > 0 ? (wins / count) * 100 : 0;
  const profitFactor = totalLoss > 0 ? totalWin / totalLoss : (totalWin > 0 ? Infinity : 0);
  const avgR = rCount > 0 ? sumR / rCount : 0;
  const avgWin  = wins > 0 ? totalWin / wins : 0;
  const avgLoss = losses > 0 ? totalLoss / losses : 0;
  // Espérance = P&L moyen par trade (sur tous les trades, y compris break-even).
  const expectancy = count > 0 ? netPnl / count : 0;
  return { netPnl, winRate, profitFactor, avgR, expectancy, count, wins, losses, avgWin, avgLoss };
}

async function getStats({ from, to } = {}) {
  const trades = await listTrades({ from, to });
  return computeStats(trades);
}

// Jour civil LOCAL (fuseau du profil, fallback Europe/Paris) d'un executed_at.
// On NE fait PAS un slice(0,10) brut : executed_at est stocké en UTC, donc le
// slice rangerait un trade de 01:00 Paris à la veille. localDayInTz projette
// l'instant dans le fuseau de l'user → cohérent avec le calendrier et weekly.jsx
// (qui groupent déjà par jour local). Robuste à l'historique déjà en UTC.
function localDayKey(iso, tz) {
  if (!iso) return '';
  if (typeof localDayInTz === 'function') {
    try { return localDayInTz(iso, tz || (typeof window !== 'undefined' && window.__appTz) || 'Europe/Paris'); }
    catch (e) { /* fallback ci-dessous */ }
  }
  const d = new Date(iso);
  if (isNaN(d.getTime())) return String(iso).slice(0, 10);
  const y = d.getFullYear(), m = String(d.getMonth() + 1).padStart(2, '0'), dd = String(d.getDate()).padStart(2, '0');
  return `${y}-${m}-${dd}`;
}

// Group trades by LOCAL ISO day for the calendar grid.
function groupTradesByDay(trades, tz) {
  const out = {};
  for (const t of trades) {
    const day = localDayKey(t.executed_at, tz);
    if (!day) continue;
    if (!out[day]) out[day] = { day, trades: [], pnl: 0, wins: 0, losses: 0 };
    out[day].trades.push(t);
    out[day].pnl += Number(t.pnl) || 0;
    if (Number(t.pnl) > 0) out[day].wins++;
    else if (Number(t.pnl) < 0) out[day].losses++;
  }
  return out;
}

Object.assign(window, {
  listTrades, getTrade, addTrade, updateTrade, deleteTrade,
  bulkInsertTrades, computeStats, getStats, groupTradesByDay, localDayKey,
});
