const BASE = '/api';

async function request<T>(path: string, options?: RequestInit): Promise<T> {
  const res = await fetch(`${BASE}${path}`, {
    headers: { 'Content-Type': 'application/json', ...options?.headers },
    ...options,
  });
  if (!res.ok) {
    const err = await res.json().catch(() => ({ detail: res.statusText }));
    throw new Error(err.detail || `HTTP ${res.status}`);
  }
  return res.json();
}

// --- Experiments ---
export interface ExperimentMeta {
  name: string;
  id: number | null;
  parent: string | null;
  version_family: string | null;
  generation: number | null;
  status: string;
  created_at: string | null;
  created_by: string | null;
  tags: string[];
  aliases: string[];
  description: string | null;
  changelog: string | null;
  has_journal_entry: boolean;
  sqs_score: number | null;
}

export interface ExperimentConfig {
  experiment_name: string;
  dataset_snapshot_id: string;
  base_config: string;
  description: string | null;
  overrides: Record<string, unknown>;
  strategy_engines: Record<string, unknown>[];
  splits: unknown[];
  tags: string[];
  notes: string | null;
  id: number | null;
  parent: string | null;
  status: string;
  generation: number | null;
  version_family: string | null;
  created_at: string | null;
  aliases: string[];
  changelog: string | null;
  performance_summary: Record<string, unknown> | null;
}

export interface LineageNode {
  name: string;
  meta: Partial<ExperimentMeta>;
  depth: number;
  children: LineageNode[];
}

export const experimentsApi = {
  list: (params?: {
    status?: string; family?: string; tag?: string; pattern?: string;
    sort_by?: string; limit?: number; offset?: number; has_journal?: boolean;
    include_retired?: boolean;
  }) => {
    const q = new URLSearchParams();
    if (params?.status) q.set('status', params.status);
    if (params?.family) q.set('family', params.family);
    if (params?.tag) q.set('tag', params.tag);
    if (params?.pattern) q.set('pattern', params.pattern);
    if (params?.sort_by) q.set('sort_by', params.sort_by);
    if (params?.limit != null) q.set('limit', String(params.limit));
    if (params?.offset != null) q.set('offset', String(params.offset));
    if (params?.has_journal != null) q.set('has_journal', String(params.has_journal));
    if (params?.include_retired != null) q.set('include_retired', String(params.include_retired));
    return request<{ experiments: ExperimentMeta[]; total: number }>(`/experiments?${q}`);
  },

  get: (name: string) => request<ExperimentConfig>(`/experiments/${encodeURIComponent(name)}`),

  lineage: (name: string) =>
    request<{ tree: LineageNode; ancestor_chain: unknown[]; current: string }>(
      `/experiments/${encodeURIComponent(name)}/lineage`,
    ),

  diff: (a: string, b: string) =>
    request<Record<string, [unknown, unknown]>>(
      `/experiments/${encodeURIComponent(a)}/diff/${encodeURIComponent(b)}`,
    ),

  setStatus: (name: string, status: string, alias?: string) =>
    request<{ name: string; status: string }>(`/experiments/${encodeURIComponent(name)}/status`, {
      method: 'PUT',
      body: JSON.stringify({ status, alias }),
    }),

  create: (parent: string, name: string, changelog?: string) =>
    request<{ name: string; parent: string }>('/experiments', {
      method: 'POST',
      body: JSON.stringify({ parent, name, changelog }),
    }),

  compose: (body: {
    base: string; name: string; changelog?: string;
    parking?: string | null; idle_alpha?: string | null;
    form4_sleeve?: string | null; ownership_sleeve?: string | null;
    risk_off_sleeve?: string | null;
  }) =>
    request<{ name: string; parent: string }>('/experiments/compose', {
      method: 'POST',
      body: JSON.stringify(body),
    }),

  update: (name: string, config: ExperimentConfig) =>
    request<{ name: string; updated: boolean }>(`/experiments/${encodeURIComponent(name)}`, {
      method: 'PUT',
      body: JSON.stringify(config),
    }),

  delete: (name: string) =>
    request<{ name: string; deleted: boolean }>(`/experiments/${encodeURIComponent(name)}`, {
      method: 'DELETE',
    }),

  families: () => request<{ families: string[] }>('/experiments/families'),
  tags: () => request<{ tags: string[] }>('/experiments/tags'),
  rebuildIndex: () => request<{ rebuilt: boolean }>('/experiments/rebuild-index', { method: 'POST' }),
};

// --- Leaderboard ---
export interface LeaderboardEntry {
  rank: number;
  entry_id: string;
  config_id: number | null;
  experiment_name: string;
  strategy_family: string;
  is_retired: boolean;
  sqs_score: number | null;
  reset_common_window_score: number | null;
  rqs_score: number | null;
  wfqs_score: number | null;
  deployment_score: number | null;
  promotion_score: number | null;
  // test split
  total_return_pct: number | null;
  annualized_return_pct: number | null;
  win_rate: number | null;
  sharpe_ratio: number | null;
  max_drawdown_pct: number | null;
  trade_count: number;
  avg_gross_exposure_pct: number | null;
  days_in_market_pct: number | null;
  profit_factor: number | null;
  // train
  train_total_return_pct: number | null;
  // valid split
  valid_total_return_pct: number | null;
  timestamp: string;
}

export const leaderboardApi = {
  list: (params?: { sort_by?: string; include_retired?: boolean; top?: number; offset?: number }) => {
    const q = new URLSearchParams();
    if (params?.sort_by) q.set('sort_by', params.sort_by);
    if (params?.include_retired) q.set('include_retired', String(params.include_retired));
    if (params?.top != null) q.set('top', String(params.top));
    if (params?.offset != null) q.set('offset', String(params.offset));
    return request<{ entries: LeaderboardEntry[]; total: number; sort_by: string }>(`/leaderboard?${q}`);
  },

  getEntry: (entryId: string) =>
    request<Record<string, unknown>>(`/leaderboard/entry/${encodeURIComponent(entryId)}`),

  getExperimentEntry: (name: string) =>
    request<Record<string, unknown>>(`/leaderboard/experiment/${encodeURIComponent(name)}`),

  refresh: () => request<{ entries: number; rebuilt: boolean }>('/leaderboard/refresh', { method: 'POST' }),
};

// --- Runs ---
export const runsApi = {
  list: (params?: { experiment?: string; limit?: number }) => {
    const q = new URLSearchParams();
    if (params?.experiment) q.set('experiment', params.experiment);
    if (params?.limit != null) q.set('limit', String(params.limit));
    return request<{ runs: unknown[]; total: number }>(`/runs?${q}`);
  },
  metadata: (runId: string) => request<Record<string, unknown>>(`/runs/${runId}/metadata`),
  metrics: (runId: string) => request<Record<string, unknown>>(`/runs/${runId}/metrics`),
  equityCurve: (runId: string) => request<{ data: Record<string, unknown>[]; format: string }>(`/runs/${runId}/equity-curve`),
  tradeBlotter: (runId: string, params?: { limit?: number; offset?: number }) => {
    const q = new URLSearchParams();
    if (params?.limit != null) q.set('limit', String(params.limit));
    if (params?.offset != null) q.set('offset', String(params.offset));
    return request<{ trades: Record<string, unknown>[]; total: number }>(`/runs/${runId}/trade-blotter?${q}`);
  },
  perEngineMetrics: (runId: string) => request<Record<string, unknown>>(`/runs/${runId}/per-engine-metrics`),
};

// --- Backtest ---
export interface BacktestRequest {
  experiment_name: string;
  capital?: number;
  start?: string | null;    // YYYY-MM-DD or YYYY
  end?: string | null;      // YYYY-MM-DD
  year?: string | null;     // YYYY shorthand
  no_trades?: boolean;
  mode?: string;            // "cli" (default) or "direct" (in-process)
  parking?: string | null;  // cash parking preset name
  idle_alpha?: string | null;  // idle alpha sleeve preset name
  form4_sleeve?: string | null; // Form 4 residual-cash sleeve preset name
  ownership_sleeve?: string | null; // 13D/13G ownership sleeve preset name
  risk_off_sleeve?: string | null; // risk-off alpha sleeve preset name
  non_core_allocator_v2_mode?: 'shadow' | 'live' | null;
  snapshot_id?: string | null; // override dataset snapshot
  fixed_capital?: boolean;
}

export interface BacktestLastParams {
  capital?: number | null;
  start?: string | null;
  end?: string | null;
  year?: string | null;
}

export interface BacktestTask {
  task_id: string;
  experiment_name: string;
  capital: number;
  start_date: string | null;
  end_date: string | null;
  year: string | null;
  no_trades: boolean;
  status: 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
  created_at: string;
  started_at: string | null;
  finished_at: string | null;
  run_id: string | null;
  error: string | null;
  mode?: 'cli' | 'direct';
  has_direct_result?: boolean;
  fixed_capital?: boolean;
  parking?: string | null;
  idle_alpha?: string | null;
  form4_sleeve?: string | null;
  ownership_sleeve?: string | null;
  risk_off_sleeve?: string | null;
  non_core_allocator_v2_mode?: 'shadow' | 'live' | null;
  snapshot_id?: string | null;
  result_summary?: {
    return_pct: number | null;
    max_dd_pct: number | null;
    sharpe: number | null;
    win_rate: number | null;
    trade_count: number | null;
  } | null;
}

export interface DirectResult {
  session_name: string;
  initial_equity: number;
  equity_curve: { date: string; equity: number }[];
  trades: Record<string, unknown>[];
  summary: {
    return_pct: number;
    final_equity: number;
    max_dd_pct: number;
    trade_count: number;
    win_rate: number;
    sharpe: number;
  };
  metrics_bundle: Record<string, unknown>;
}

export const backtestApi = {
  submit: (req: BacktestRequest) =>
    request<BacktestTask>('/backtest/submit', { method: 'POST', body: JSON.stringify(req) }),

  submitBatch: (names: string[], params: Omit<BacktestRequest, 'experiment_name'>) =>
    request<{ tasks: BacktestTask[] }>('/backtest/submit-batch', {
      method: 'POST',
      body: JSON.stringify({ experiment_names: names, ...params }),
    }),

  tasks: () => request<{ tasks: BacktestTask[] }>('/backtest/tasks'),

  task: (id: string) => request<BacktestTask>(`/backtest/tasks/${id}`),

  cancel: (id: string) =>
    request<{ cancelled: boolean }>(`/backtest/tasks/${id}`, { method: 'DELETE' }),

  taskLog: (id: string) =>
    request<{ log: string; total_lines?: number }>(`/backtest/tasks/${id}/log`),

  lastParams: (name: string) =>
    request<BacktestLastParams>(`/backtest/last-params/${encodeURIComponent(name)}`),

  directResult: (taskId: string) =>
    request<DirectResult>(`/backtest/tasks/${taskId}/direct-result`),

  parkingPresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/parking-presets'),

  idleAlphaPresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/idle-alpha-presets'),

  form4CapturePresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/form4-capture-presets'),

  ownershipCapturePresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/ownership-capture-presets'),

  riskOffAlphaPresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/risk-off-alpha-presets'),

  snapshots: () =>
    request<{ snapshots: { id: string; start: string | null; end: string | null; rows: number | null }[] }>('/backtest/snapshots'),
};

// --- Docs ---
export const docsApi = {
  get: (filename: string) =>
    request<{ content: string; filename: string }>(`/docs/${encodeURIComponent(filename)}`),
};

// --- Paper Trading ---

export interface PaperSession {
  session_id: string;
  session_name: string;
  config_path: string;
  initial_equity: number;
  created_at: string;
  status: 'active' | 'paused' | 'closed';
  parking_preset: string | null;
  idle_alpha_preset: string | null;
  form4_sleeve_preset: string | null;
  ownership_sleeve_preset: string | null;
  risk_off_alpha_sleeve_preset: string | null;
  current_equity: number;
  peak_equity: number;
  total_pnl: number;
  total_pnl_pct: number;
  drawdown_pct: number;
  trade_count: number;
  latest_date: string | null;
  kill_switch: boolean;
  cooldown_remaining: number;
  consecutive_losses: number;
  daily_new_risk_used: number;
  last_processed_date: string | null;
}

export interface PaperPosition {
  symbol: string;
  qty: number | null;
  avg_entry_price: number | null;
  current_price: number | null;
  unrealized_pl: number | null;
  unrealized_pl_pct: number | null;
  days_held: number | null;
  stop_price: number | null;
  target_price: number | null;
  entry_date: string | null;
  engine_id: string | null;
  trade_direction: string | null;
  _ghost?: boolean;
}

export interface PaperTrade {
  trade_id: string;
  session_id: string;
  symbol: string;
  engine_id: string | null;
  trade_sleeve?: string | null;
  entry_date: string | null;
  exit_date: string;
  entry_price: number | null;
  exit_price: number;
  exit_reason: string;
  shares: number;
  net_pnl: number;
  r_multiple: number;
  holding_days: number;
}

export interface PaperSnapshot {
  session_id: string;
  date: string;
  equity: number;
  cash: number;
  market_value: number;
  daily_pnl: number | null;
  total_pnl: number | null;
  drawdown_pct: number | null;
  open_position_count: number | null;
}

export interface PaperTask {
  task_id: string;
  session_name: string;
  operation: string;
  status: 'running' | 'completed' | 'failed';
  created_at: string;
  started_at: string | null;
  finished_at: string | null;
  log?: string;
  error: string | null;
}

export interface PaperConfig {
  name: string;
  path: string;
  id: number | null;
  status: string;
  version_family: string | null;
  generation: number | null;
  parent: string | null;
}

export const paperApi = {
  // Sessions
  sessions: () => request<{ sessions: PaperSession[] }>('/paper/sessions'),

  createSession: (
    name: string,
    config: string,
    capital: number,
    parking?: string | null,
    idleAlpha?: string | null,
    form4Sleeve?: string | null,
    ownershipSleeve?: string | null,
    riskOffSleeve?: string | null,
  ) =>
    request<{ session_id: string; name: string; capital: number; config: string; parking?: string; idle_alpha?: string; form4_sleeve?: string; ownership_sleeve?: string; risk_off_sleeve?: string }>(
      '/paper/sessions',
      {
        method: 'POST',
        body: JSON.stringify({
          name,
          config,
          capital,
          parking: parking || null,
          idle_alpha: idleAlpha || null,
          form4_sleeve: form4Sleeve || null,
          ownership_sleeve: ownershipSleeve || null,
          risk_off_sleeve: riskOffSleeve || null,
        }),
      },
    ),

  parkingPresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/parking-presets'),

  idleAlphaPresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/idle-alpha-presets'),

  form4CapturePresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/form4-capture-presets'),

  ownershipCapturePresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/ownership-capture-presets'),

  riskOffAlphaPresets: () =>
    request<{ presets: string[]; groups: Record<string, string[]> }>('/backtest/risk-off-alpha-presets'),

  getSession: (sessionId: string) =>
    request<PaperSession>(`/paper/sessions/${encodeURIComponent(sessionId)}`),

  pauseSession: (sessionId: string) =>
    request<{ session_id: string; status: string }>(`/paper/sessions/${encodeURIComponent(sessionId)}/pause`, { method: 'POST' }),

  resumeSession: (sessionId: string) =>
    request<{ session_id: string; status: string }>(`/paper/sessions/${encodeURIComponent(sessionId)}/resume`, { method: 'POST' }),

  closeSession: (sessionId: string) =>
    request<{ deleted: boolean; positions_closed: number }>(`/paper/sessions/${encodeURIComponent(sessionId)}`, { method: 'DELETE' }),

  // Data
  positions: (sessionId: string) =>
    request<{ positions: PaperPosition[]; broker_available: boolean; broker_error?: string }>(
      `/paper/sessions/${encodeURIComponent(sessionId)}/positions`,
    ),

  trades: (sessionId: string, last?: number) => {
    const q = last != null ? `?last=${last}` : '';
    return request<{ trades: PaperTrade[]; total: number }>(
      `/paper/sessions/${encodeURIComponent(sessionId)}/trades${q}`,
    );
  },

  equity: (sessionId: string) =>
    request<{ snapshots: PaperSnapshot[]; initial_equity: number }>(
      `/paper/sessions/${encodeURIComponent(sessionId)}/equity`,
    ),

  // Run operations
  run: (sessionId: string, date?: string | null, force?: boolean) =>
    request<PaperTask>(`/paper/sessions/${encodeURIComponent(sessionId)}/run`, {
      method: 'POST',
      body: JSON.stringify({ date: date || null, force: force ?? false }),
    }),

  runClose: (sessionId: string, date?: string | null, force?: boolean) =>
    request<PaperTask>(`/paper/sessions/${encodeURIComponent(sessionId)}/run-close`, {
      method: 'POST',
      body: JSON.stringify({ date: date || null, force: force ?? false }),
    }),

  runOpen: (sessionId: string, date?: string | null, force?: boolean) =>
    request<PaperTask>(`/paper/sessions/${encodeURIComponent(sessionId)}/run-open`, {
      method: 'POST',
      body: JSON.stringify({ date: date || null, force: force ?? false }),
    }),

  runAll: () => request<{ tasks: PaperTask[] }>('/paper/run-all', { method: 'POST' }),

  // Tasks
  tasks: (sessionName?: string) => {
    const q = sessionName ? `?session_name=${encodeURIComponent(sessionName)}` : '';
    return request<{ tasks: PaperTask[] }>(`/paper/tasks${q}`);
  },

  task: (taskId: string) => request<PaperTask>(`/paper/tasks/${taskId}`),

  taskLog: (taskId: string) =>
    request<{ log: string; status: string }>(`/paper/tasks/${taskId}/log`),

  // Auto daemon
  autoStatus: () =>
    request<{
      running: boolean; pid: number | null;
      schedule: { name: string; label: string; et_time: string; et_iso: string; past: boolean; wait_secs: number }[];
      log_tail: string[];
    }>('/paper/auto'),

  autoStart: (sessions?: string[], dryRun?: boolean) =>
    request<{ started: boolean; pid: number; dry_run: boolean; sessions: string[] }>('/paper/auto/start', {
      method: 'POST',
      body: JSON.stringify({ sessions: sessions ?? [], dry_run: dryRun ?? false }),
    }),

  autoStop: () =>
    request<{ stopped: boolean; pid: number }>('/paper/auto/stop', { method: 'POST' }),

  autoLog: (lines?: number) =>
    request<{ log: string; lines: number; source?: string; tty?: boolean }>(`/paper/auto/log${lines ? `?lines=${lines}` : ''}`),

  // Configs
  configs: (params?: { status?: string; pattern?: string; limit?: number }) => {
    const q = new URLSearchParams();
    if (params?.status) q.set('status', params.status);
    if (params?.pattern) q.set('pattern', params.pattern);
    if (params?.limit != null) q.set('limit', String(params.limit));
    return request<{ configs: PaperConfig[]; total: number }>(`/paper/configs?${q}`);
  },
};

// --- SQS ---
export interface SqsPillars {
  rqs_score: number | null;
  wfqs_v2_score: number | null;
  regime_score: number | null;
  regime_source: string;
  core_score: number | null;
}

export interface SqsGates {
  deployment_gate_factor: number | null;
  robustness_gate_factor: number | null;
  activity_factor: number | null;
}

export interface SqsResult {
  experiment_name: string;
  entry_id: string;
  sqs_score: number | null;
  source: string | null;
  pending_reason: string | null;
  pillars: SqsPillars;
  gates: SqsGates;
  breakdown: Record<string, number>;
  stored_sqs_score: number | null;
  scenario_robustness_score: number | null;
  overfit_check_score: number | null;
  entries_updated?: number;
}

export const sqsApi = {
  compute: (name: string) =>
    request<SqsResult>(`/sqs/${encodeURIComponent(name)}`),

  rescore: (target: string) =>
    request<SqsResult>('/sqs/rescore', {
      method: 'POST',
      body: JSON.stringify({ target }),
    }),

  pipeline: (target: string) =>
    request<SqsResult & { pipeline_id?: string; pipeline_status: string; step?: string }>('/sqs/pipeline', {
      method: 'POST',
      body: JSON.stringify({ target }),
    }),

  pollPipeline: (pipelineId: string) =>
    request<SqsResult & { pipeline_id: string; pipeline_status: string; step?: string; error?: string }>(
      `/sqs/pipeline/${pipelineId}`
    ),
};
