/**
 * GoldenChart Backend API 클라이언트.
 * Spring Boot 백엔드와 통신한다.
 *
 * 환경변수 VITE_API_BASE_URL 로 백엔드 URL 설정 (기본: http://localhost:8080/api)
 */

/** 백엔드 REST base (Docker 빌드: `/api`, 로컬 Vite: `.env` 또는 localhost:8080) */
export const API_BASE = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:8080/api';

/** STOMP WebSocket 연결에 사용할 HTTP base URL (api 경로 제거) */
export function getApiBase(): string {
  return API_BASE.replace(/\/api$/, '');
}

/**
 * STOMP SockJS 엔드포인트.
 * Spring Boot context-path가 /api 이므로 반드시 /api/ws/trading 경로를 사용한다.
 */
export function getStompSockJsUrl(): string {
  const base = API_BASE.replace(/\/$/, '');
  const path = base.endsWith('/api') ? `${base}/ws/trading` : `${base}/api/ws/trading`;
  // SockJS: 배포 환경에서 상대 경로만 쓰면 프록시/오리진 불일치 시 연결 실패가 난다.
  if (typeof window !== 'undefined' && window.location?.origin) {
    return `${window.location.origin}${path.startsWith('/') ? path : `/${path}`}`;
  }
  return path;
}

// UUID 생성 — HTTPS/localhost: crypto.randomUUID() 사용, HTTP: 폴리필
function generateUUID(): string {
  if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
    return crypto.randomUUID();
  }
  // HTTP 환경(비보안 컨텍스트) 폴리필
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
    const r = (Math.random() * 16) | 0;
    const v = c === 'x' ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}

// 기기 ID (localStorage 영속)
function getDeviceId(): string {
  let id = localStorage.getItem('gc_device_id');
  if (!id) {
    id = generateUUID();
    localStorage.setItem('gc_device_id', id);
  }
  return id;
}

function authHeaders(): Record<string, string> {
  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
    'x-device-id': getDeviceId(),
  };
  const userId = localStorage.getItem('gc_user_id');
  if (userId) headers['x-user-id'] = userId;
  return headers;
}

/** 로그인 API 오류 시 메시지 throw */
async function requestOrThrow<T>(path: string, init?: RequestInit): Promise<T> {
  const res = await fetch(`${API_BASE}${path}`, {
    ...init,
    headers: { ...authHeaders(), ...(init?.headers ?? {}) },
  });
  const text = await res.text().catch(() => '');
  if (!res.ok) {
    let msg = `요청 실패 (${res.status})`;
    try {
      const j = JSON.parse(text) as { message?: string };
      if (j.message) msg = j.message;
    } catch { /* plain text */ }
    if (!text.startsWith('{') && text) msg = text.slice(0, 200);
    throw new Error(msg);
  }
  if (res.status === 204 || !text) return undefined as T;
  return JSON.parse(text) as T;
}

async function request<T>(path: string, init?: RequestInit): Promise<T | null> {
  try {
    const res = await fetch(`${API_BASE}${path}`, {
      ...init,
      headers: { ...authHeaders(), ...(init?.headers ?? {}) },
    });
    if (res.status === 204) return null;
    if (!res.ok) {
      console.error(`[backendApi] ${init?.method ?? 'GET'} ${path} → ${res.status}`);
      return null;
    }
    return (await res.json()) as T;
  } catch (e) {
    console.error(`[backendApi] network error ${path}`, e);
    return null;
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// 타입 정의 (backend DTO 와 동기화)
// ─────────────────────────────────────────────────────────────────────────────

export interface SlotDto {
  slotIndex:       number;
  symbol:          string | null;
  timeframe:       string | null;
  chartType:       string;
  theme:           string;
  mode:            string;
  logScale:        boolean;
  drawingsLocked:  boolean;
  drawingsVisible: boolean;
  indicators:      unknown;   // IndicatorConfig[]
  drawings:        unknown;   // Drawing[]
  paneLayout:      unknown;   // PaneLayoutEntry[]
  mainChartStyle:  unknown;   // MainChartStyle
}

export interface WorkspaceDto {
  id:          number;
  layoutId:    string;
  syncOptions: unknown;
  slots:       SlotDto[];
}

export interface WatchlistItem {
  id:           number;
  symbol:       string;
  koreanName:   string | null;
  englishName:  string | null;
  displayOrder: number;
}

export interface HoldingsItem extends WatchlistItem {
  avgPrice: number | null;
  quantity: number | null;
}

export interface IndicatorPoint {
  time:  number;
  value: number | null;
  color: string | null;
}

export interface IndicatorCalcResponse {
  symbol:        string;
  timeframe:     string;
  indicatorType: string;
  times:         number[];
  values:        Record<string, IndicatorPoint[]>;
}

// ─────────────────────────────────────────────────────────────────────────────
// 워크스페이스 API
// ─────────────────────────────────────────────────────────────────────────────

/** 프론트 시작 시 DB에서 전체 설정 로드 */
export async function loadWorkspace(): Promise<WorkspaceDto | null> {
  return request<WorkspaceDto>('/chart/workspace');
}

/** 전체 워크스페이스 저장 */
export async function saveWorkspace(data: {
  layoutId:    string;
  syncOptions: unknown;
  slots:       unknown[];
}): Promise<WorkspaceDto | null> {
  return request<WorkspaceDto>('/chart/workspace', {
    method: 'POST',
    body:   JSON.stringify(data),
  });
}

/** 레이아웃만 변경 */
export async function updateLayout(layoutId: string, syncOptions?: unknown): Promise<void> {
  await request('/chart/workspace/layout', {
    method: 'PUT',
    body:   JSON.stringify({ layoutId, syncOptions }),
  });
}

/** 특정 슬롯 저장 */
export async function saveSlot(slotIndex: number, slotData: Partial<SlotDto>): Promise<void> {
  await request(`/chart/slot/${slotIndex}`, {
    method: 'PUT',
    body:   JSON.stringify(slotData),
  });
}

// ─────────────────────────────────────────────────────────────────────────────
// 관심종목 API
// ─────────────────────────────────────────────────────────────────────────────

export async function loadWatchlist(): Promise<WatchlistItem[]> {
  return (await request<WatchlistItem[]>('/watchlist')) ?? [];
}

export async function addWatchlistItem(item: {
  symbol:       string;
  koreanName?:  string;
  englishName?: string;
}): Promise<WatchlistItem | null> {
  return request<WatchlistItem>('/watchlist', {
    method: 'POST',
    body:   JSON.stringify(item),
  });
}

export async function removeWatchlistItem(symbol: string): Promise<void> {
  await request(`/watchlist/${encodeURIComponent(symbol)}`, { method: 'DELETE' });
}

export async function reorderWatchlist(
  items: { symbol: string; displayOrder: number }[]
): Promise<void> {
  await request('/watchlist/order', { method: 'PUT', body: JSON.stringify(items) });
}

// ─────────────────────────────────────────────────────────────────────────────
// 보유종목 API
// ─────────────────────────────────────────────────────────────────────────────

export async function loadHoldings(): Promise<HoldingsItem[]> {
  return (await request<HoldingsItem[]>('/holdings')) ?? [];
}

export async function upsertHoldingsItem(item: {
  symbol:       string;
  koreanName?:  string;
  englishName?: string;
  avgPrice?:    number;
  quantity?:    number;
}): Promise<HoldingsItem | null> {
  return request<HoldingsItem>('/holdings', {
    method: 'POST',
    body:   JSON.stringify(item),
  });
}

export async function removeHoldingsItem(symbol: string): Promise<void> {
  await request(`/holdings/${encodeURIComponent(symbol)}`, { method: 'DELETE' });
}

// ─────────────────────────────────────────────────────────────────────────────
// 지표 계산 API (백엔드 Ta4j 사용)
// ─────────────────────────────────────────────────────────────────────────────

export async function calculateIndicator(req: {
  symbol?:        string;
  timeframe?:     string;
  indicatorType:  string;
  params?:        Record<string, unknown>;
  bars?:          { time: number; open: number; high: number; low: number; close: number; volume: number }[];
}): Promise<IndicatorCalcResponse | null> {
  return request<IndicatorCalcResponse>('/indicators/calculate', {
    method: 'POST',
    body:   JSON.stringify(req),
  });
}

// ─────────────────────────────────────────────────────────────────────────────
// 전역 지표 파라미터 설정 API
//
// 역할: 프론트엔드 indicatorRegistry.ts 의 defaultParams 를 완전히 대체.
//       사용자가 변경한 파라미터를 DB 에 저장하고,
//       백엔드 Ta4j 계산 시에도 동일한 값을 사용하도록 한다.
//
// 저장 형식:
// {
//   "RSI":           { "length": 9, "src": "close" },
//   "MACD":          { "fastLength": 12, "slowLength": 26, "signalLength": 9 },
//   "BollingerBands":{ "length": 20, "mult": 2.0, "src": "close" },
//   ...
// }
// ─────────────────────────────────────────────────────────────────────────────

/** 모든 지표 파라미터 로드 (앱 시작 시 호출) */
export async function loadIndicatorSettings(): Promise<
  Record<string, Record<string, unknown>>
> {
  return (
    (await request<Record<string, Record<string, unknown>>>('/indicator-settings')) ?? {}
  );
}

/** 특정 지표 파라미터 로드 */
export async function loadIndicatorSettingsForType(
  indicatorType: string
): Promise<Record<string, unknown>> {
  return (
    (await request<Record<string, unknown>>(`/indicator-settings/${encodeURIComponent(indicatorType)}`)) ?? {}
  );
}

/**
 * 전체 지표 파라미터 저장 (덮어쓰기).
 * 앱 초기화 또는 설정 내보내기/가져오기 시 사용.
 */
export async function saveAllIndicatorSettings(
  allParams: Record<string, Record<string, unknown>>
): Promise<void> {
  await request('/indicator-settings', {
    method: 'PUT',
    body:   JSON.stringify(allParams),
  });
}

/**
 * 특정 지표 파라미터 저장 (병합).
 * 사용자가 특정 지표의 설정값을 변경할 때마다 호출.
 *
 * @param indicatorType  지표 타입 (e.g. "RSI", "MACD")
 * @param params         변경된 파라미터 (e.g. {length: 9, src: "close"})
 */
export async function saveIndicatorSettings(
  indicatorType: string,
  params: Record<string, unknown>
): Promise<void> {
  await request(`/indicator-settings/${encodeURIComponent(indicatorType)}`, {
    method: 'PATCH',
    body:   JSON.stringify(params),
  });
}

/**
 * 모든 지표의 시각 설정(색상·선굵기·수평선) 로드.
 * 앱 시작 시 호출 — 전역 시각 기본값을 DB 에서 가져온다.
 */
export async function loadIndicatorVisualSettings(): Promise<
  Record<string, { plots?: unknown[]; hlines?: unknown[] }>
> {
  return (
    (await request<Record<string, { plots?: unknown[]; hlines?: unknown[] }>>(
      '/indicator-settings/visual'
    )) ?? {}
  );
}

/**
 * 특정 지표의 시각 설정(색상·선굵기·수평선)을 저장.
 * 사용자가 IndicatorSettingsModal 에서 색상·선굵기를 변경할 때 호출.
 *
 * @param indicatorType  지표 타입 (e.g. "RSI")
 * @param visual         { plots: PlotDef[], hlines: HLineDef[] }
 */
export async function saveIndicatorVisualSettings(
  indicatorType: string,
  visual: { plots?: unknown[]; hlines?: unknown[] }
): Promise<void> {
  await request(`/indicator-settings/${encodeURIComponent(indicatorType)}/visual`, {
    method: 'PATCH',
    body:   JSON.stringify(visual),
  });
}

// ─────────────────────────────────────────────────────────────────────────────
// 앱 전역 차트 기본 설정 API
//
// 프론트엔드 코드에 하드코딩된 기본값들을 DB 로 대체.
//
// 관리 항목:
//   defaultSymbol    — 기본 종목 (e.g. "KRW-BTC")
//   defaultTimeframe — 기본 타임프레임 (e.g. "1D")
//   defaultChartType — 기본 차트 타입 (e.g. "candlestick")
//   defaultTheme     — 기본 테마 (e.g. "dark")
//   defaultLogScale  — 기본 로그 스케일 (false)
//   defaultLayoutId  — 기본 레이아웃 ID (e.g. "1")
//   mainChartStyle   — 캔들 색상 (DEFAULT_MAIN_CHART_STYLE 대체)
//   syncOptions      — 멀티차트 동기화 옵션 (DEFAULT_SYNC 대체)
// ─────────────────────────────────────────────────────────────────────────────

export interface AppSettingsDto {
  defaultSymbol?:    string;
  defaultTimeframe?: string;
  defaultChartType?: string;
  defaultTheme?:     string;
  defaultLogScale?:  boolean;
  defaultLayoutId?:  string;
  mainChartStyle?:   Record<string, string> | null;
  syncOptions?:      Record<string, boolean> | null;
  /** 백테스팅 완료 시 결과 팝업 자동 표시 여부 (기본 true) */
  btAutoPopup?:      boolean;
  /** 백테스팅 매수/매도 마커에 금액 표시 여부 (기본 true) */
  btShowPrice?:      boolean;
  /** 보조지표 우측 가격축 라벨·설명·금액 하이라이트 (기본 true) */
  chartSeriesPriceLabels?: boolean;
  /** 차트 하단 거래량 바 표시 (기본 true) */
  chartVolumeVisible?: boolean;
  /** 차트 상단 범례(tv-legend) 항목별 표시 옵션 */
  chartLegendOptions?: Record<string, boolean> | null;
  /** 매매 시그널 발생 시 알림 팝업 표시 여부 (기본 true) */
  tradeAlertPopup?:  boolean;
  /** 매매 시그널 알림 사운드 ON/OFF */
  tradeAlertSoundEnabled?: boolean;
  /** 매매 시그널 알림음 ID */
  tradeAlertSound?: string;
  /** 알림 팝업 표시 위치: right | left | bottom */
  tradeAlertPopupPosition?: string;
  /** 알림 팝업 배치: stack | grid | strip | single */
  tradeAlertPopupLayout?: string;
  /** 그리드 배치 열 개수 (2~4) */
  tradeAlertPopupGridCols?: number;
  /** 실시간 전략 체크 마스터 ON — ON이면 DB 관심종목 전체가 체크 대상 */
  liveStrategyCheck?:   boolean;
  /** 관심종목 공통 전략 ID */
  liveStrategyId?:       number | null;
  liveExecutionType?:  string;
  livePositionMode?:     string;
  /** 모의투자 */
  paperTradingEnabled?:     boolean;
  paperInitialCapital?:     number;
  paperFeeRatePct?:          number;
  paperSlippagePct?:         number;
  paperMinOrderKrw?:        number;
  paperAutoTradeEnabled?:   boolean;
  paperAutoTradeBudgetPct?: number;
  /** PAPER | LIVE | BOTH */
  tradingMode?:            string;
  liveAutoTradeEnabled?:   boolean;
  upbitAccessKeyMasked?:   string | null;
  hasUpbitKeys?:           boolean;
  upbitAccessKey?:         string;
  upbitSecretKey?:         string;
  /** BACKEND_STOMP | UPBIT_DIRECT */
  chartRealtimeSource?:    string;
  liveAutoTradeBudgetPct?: number;
  fcmPushEnabled?:         boolean;
  /** 차트·UI 표시 시간대 (IANA, 예: Asia/Seoul) */
  displayTimezone?:        string;
}

export interface LiveSummaryDto {
  enabled:     boolean;
  configured:  boolean;
  tradingMode: string;
  krwBalance:  number;
  positions:   { symbol: string; quantity: number; avgPrice: number }[];
}

export interface LiveTradeDto {
  id?:             number;
  symbol:          string;
  side:            string;
  source:          string;
  price:           number;
  quantity:        number;
  grossAmount:     number;
  upbitOrderUuid?: string | null;
  createdAt?:      string | null;
}

export async function loadLiveSummary(): Promise<LiveSummaryDto | null> {
  return request<LiveSummaryDto>('/live/summary');
}

export async function loadLiveTrades(): Promise<LiveTradeDto[]> {
  return (await request<LiveTradeDto[]>('/live/trades')) ?? [];
}

export interface LiveOrderRequest {
  market:     string;
  side:       'BUY' | 'SELL';
  orderKind?: string;
  krwAmount?: number;
  quantity?:  number;
  strategyId?: number;
}

export async function placeLiveOrder(req: LiveOrderRequest): Promise<LiveTradeDto | null> {
  return request<LiveTradeDto>('/live/orders', {
    method: 'POST',
    body:   JSON.stringify(req),
  });
}

export interface ProcessMonitorDto {
  id: string;
  name: string;
  layer: string;
  status: 'healthy' | 'degraded' | 'down' | 'disabled';
  metrics: Record<string, unknown>;
}

export interface SystemMonitorDto {
  pipelineEnabled: boolean;
  sampledAtMs: number;
  config: Record<string, unknown>;
  jvm: {
    heapUsedMb: number;
    heapMaxMb: number;
    heapPct: number;
    nonHeapUsedMb: number;
    threadCount: number;
    daemonThreadCount: number;
  };
  traffic: Record<string, number>;
  memory: { markets: number; series: number; totalBars: number; maxBarsPerSeries: number };
  queue: { pending: number; capacity: number; fillRatio: number; enqueued: number; dequeued: number; dropped: number };
  workers: { processed: number; errors: number; active: boolean; workerThreads: number };
  orders: {
    pending: number; submitted: number; executed: number; failed: number;
    dropped: number; workerAlive: boolean; ratePerSecond: number;
  };
  processes: ProcessMonitorDto[];
  threads: { name: string; id: number; state: string; daemon: boolean }[];
  staleMarkets: { market: string; lastTickAgeSec: number }[];
  lastTicks: Record<string, number>;
}

export interface DashboardSummaryDto {
  app: Record<string, unknown>;
  watchlistCount: number;
  liveCheckMarkets: number;
  monitoredMarkets: number;
  paper: PaperSummaryDto;
  live: LiveSummaryDto;
  recentSignals: TradeSignalDto[];
  systemMonitor: SystemMonitorDto;
  fcm: { available: boolean; pushEnabled: boolean };
}

export async function loadDashboardSummary(): Promise<DashboardSummaryDto | null> {
  return request<DashboardSummaryDto>('/dashboard/summary');
}

export async function registerFcmToken(token: string): Promise<void> {
  await request<void>('/fcm/token', {
    method: 'POST',
    body:   JSON.stringify({ token }),
  });
}

export async function loadFcmStatus(): Promise<{ available: boolean } | null> {
  return request<{ available: boolean }>('/fcm/status');
}

export async function sendFcmTest(): Promise<{ ok: boolean; available: boolean } | null> {
  return request<{ ok: boolean; available: boolean }>('/fcm/test', { method: 'POST' });
}

// ── 모의투자 ───────────────────────────────────────────────────────────────────

export interface PaperPositionDto {
  id?:           number;
  symbol:        string;
  koreanName?:   string | null;
  quantity:      number;
  avgPrice:      number;
  markPrice?:    number | null;
  evalAmount?:   number;
  profitLoss?:   number;
  profitLossPct?: number;
}

export interface PaperSummaryDto {
  enabled:              boolean;
  initialCapital:       number;
  cashBalance:          number;
  stockEvalAmount:      number;
  totalAsset:           number;
  unrealizedPnl:        number;
  realizedPnl:          number;
  totalReturnPct:       number;
  feeRatePct:           number;
  slippagePct:          number;
  minOrderKrw:          number;
  autoTradeEnabled:     boolean;
  autoTradeBudgetPct:   number;
  positions:            PaperPositionDto[];
}

export interface PaperTradeDto {
  id:           number;
  symbol:       string;
  side:         'BUY' | 'SELL';
  orderKind:    string;
  source:       string;
  strategyId?:  number | null;
  price:        number;
  quantity:     number;
  grossAmount:  number;
  feeAmount:    number;
  netAmount:    number;
  cashAfter:    number;
  createdAt?:   string | null;
}

export interface PaperOrderRequest {
  market:     string;
  side:       'BUY' | 'SELL';
  orderKind?: 'limit' | 'market';
  price:      number;
  quantity:   number;
  /** 수량 0일 때 매수 예산 비율 (가용현금 %) */
  budgetPct?: number;
  /** 수량 0일 때 매수 고정 금액 (KRW) */
  krwAmount?: number;
  source?:    string;
  strategyId?: number | null;
}

export async function loadPaperSummary(markPrices?: Record<string, number>): Promise<PaperSummaryDto | null> {
  if (markPrices && Object.keys(markPrices).length > 0) {
    return request<PaperSummaryDto>('/paper/summary', {
      method: 'POST',
      body: JSON.stringify(markPrices),
    });
  }
  return request<PaperSummaryDto>('/paper/summary');
}

export async function loadPaperTrades(): Promise<PaperTradeDto[]> {
  return (await request<PaperTradeDto[]>('/paper/trades')) ?? [];
}

export async function placePaperOrder(body: PaperOrderRequest): Promise<PaperTradeDto | null> {
  return request<PaperTradeDto>('/paper/orders', {
    method: 'POST',
    body: JSON.stringify(body),
  });
}

export async function resetPaperAccount(): Promise<PaperSummaryDto | null> {
  return request<PaperSummaryDto>('/paper/reset', { method: 'POST' });
}

/** 매매 시그널 이력 DTO */
export interface TradeSignalDto {
  id:            number;
  market:        string;
  strategyId:    number | null;
  strategyName:  string | null;
  signalType:    'BUY' | 'SELL';
  price:         number;
  candleTime:    number;
  candleType:    string;
  executionType: string;
  createdAt:     string;
}

/** 매매 시그널 이력 조회 */
export async function loadTradeSignals(market?: string): Promise<TradeSignalDto[]> {
  const q = market ? `?market=${encodeURIComponent(market)}` : '';
  return (await request<TradeSignalDto[]>(`/trade-signals${q}`)) ?? [];
}

/**
 * 앱 시작 시 기본 설정 로드.
 * DB 에 없으면 백엔드 엔티티 기본값(KRW-BTC / 1D / dark 등)을 반환.
 */
export async function loadAppSettings(): Promise<AppSettingsDto> {
  return (await request<AppSettingsDto>('/app-settings')) ?? {};
}

/**
 * 기본 설정 저장.
 * 변경된 필드만 포함한 객체를 전달 — 없는 키는 기존 값 유지.
 *
 * 호출 시점:
 *  - 테마 변경   → { defaultTheme }
 *  - 레이아웃 변경 → { defaultLayoutId }
 *  - 캔들 색상 변경 → { mainChartStyle }
 *  - 동기화 옵션 변경 → { syncOptions }
 *  - 기본 심볼/타임프레임 변경 → { defaultSymbol, defaultTimeframe }
 */
export async function saveAppSettings(data: AppSettingsDto): Promise<AppSettingsDto> {
  return (await request<AppSettingsDto>('/app-settings', {
    method: 'PUT',
    body:   JSON.stringify(data),
  })) ?? data;
}

// ─────────────────────────────────────────────────────────────────────────────
// 인증 API
// ─────────────────────────────────────────────────────────────────────────────

export interface LoginResponse {
  userId: number;
  username: string;
  displayName: string;
  role?: string;
}

export interface MenuPermissionsResponse {
  role: string;
  permissions: Record<string, boolean>;
}

export interface UserDto {
  id: number;
  username: string;
  displayName: string;
  role: string;
  enabled: boolean;
  createdAt?: string | null;
}

export async function fetchMenuPermissions(): Promise<MenuPermissionsResponse> {
  const res = await request<MenuPermissionsResponse>('/auth/menu-permissions');
  return res ?? { role: 'GUEST', permissions: { chart: true, settings: true } };
}

export async function verifyAdminPassword(password: string): Promise<void> {
  await requestOrThrow<{ ok: boolean }>('/admin/verify-password', {
    method: 'POST',
    body: JSON.stringify({ password }),
  });
}

export async function listAdminUsers(): Promise<UserDto[]> {
  return (await request<UserDto[]>('/admin/users')) ?? [];
}

export async function createAdminUser(data: {
  username: string;
  password: string;
  displayName?: string;
  role: string;
  enabled?: boolean;
}): Promise<UserDto> {
  return requestOrThrow<UserDto>('/admin/users', {
    method: 'POST',
    body: JSON.stringify(data),
  });
}

export async function updateAdminUser(
  id: number,
  data: { password?: string; displayName?: string; role?: string; enabled?: boolean },
): Promise<UserDto> {
  return requestOrThrow<UserDto>(`/admin/users/${id}`, {
    method: 'PUT',
    body: JSON.stringify(data),
  });
}

export async function deleteAdminUser(id: number): Promise<void> {
  await requestOrThrow<void>(`/admin/users/${id}`, { method: 'DELETE' });
}

export async function fetchRolePermissions(): Promise<Record<string, Record<string, boolean>>> {
  return (await request<Record<string, Record<string, boolean>>>('/admin/role-permissions')) ?? {};
}

export async function saveRolePermissions(
  role: string,
  permissions: Record<string, boolean>,
): Promise<void> {
  await requestOrThrow<void>('/admin/role-permissions', {
    method: 'PUT',
    body: JSON.stringify({ role, permissions }),
  });
}

export async function loginUser(username: string, password: string): Promise<LoginResponse> {
  return requestOrThrow<LoginResponse>('/auth/login', {
    method: 'POST',
    body: JSON.stringify({ username, password }),
  });
}

export async function fetchAuthMe(): Promise<LoginResponse | null> {
  return request<LoginResponse>('/auth/me');
}

// ─────────────────────────────────────────────────────────────────────────────
// 투자전략 API
// ─────────────────────────────────────────────────────────────────────────────

export interface StrategyDto {
  id?:            number;
  name:           string;
  description?:   string;
  buyCondition?:  unknown;   // LogicNode DSL
  sellCondition?: unknown;   // LogicNode DSL
  enabled?:       boolean;
  createdAt?:     string;
  updatedAt?:     string;
}

/** 전략 목록 로드 */
export async function loadStrategies(): Promise<StrategyDto[]> {
  return (await request<StrategyDto[]>('/strategies')) ?? [];
}

/** 단일 전략 로드 */
export async function loadStrategy(id: number): Promise<StrategyDto | null> {
  return request<StrategyDto>(`/strategies/${id}`);
}

/** 전략 저장 (id 없으면 신규, 있으면 수정) — 실패 시 Error throw */
export async function saveStrategy(dto: StrategyDto): Promise<StrategyDto> {
  const res = await fetch(`${API_BASE}/strategies`, {
    method: 'POST',
    headers: authHeaders(),
    body: JSON.stringify(dto),
  });
  if (!res.ok) {
    const body = await res.text().catch(() => '');
    const hint =
      res.status === 404
        ? '서버에 투자전략 API(/api/strategies)가 없습니다. 백엔드를 최신으로 배포했는지 확인하세요.'
        : res.status === 502 || res.status === 503
          ? '백엔드에 연결할 수 없습니다. Docker·nginx 상태를 확인하세요.'
          : `저장 실패 (HTTP ${res.status})`;
    console.error(`[backendApi] POST /strategies → ${res.status}`, body);
    throw new Error(body ? `${hint} — ${body.slice(0, 160)}` : hint);
  }
  return (await res.json()) as StrategyDto;
}

/** 전략 삭제 */
export async function deleteStrategy(id: number): Promise<void> {
  await request(`/strategies/${id}`, { method: 'DELETE' });
}

// ─────────────────────────────────────────────────────────────────────────────
// 백테스팅 API
// ─────────────────────────────────────────────────────────────────────────────

export interface BacktestSignal {
  time:     number;
  type:     'BUY' | 'SELL' | 'SHORT_ENTRY' | 'SHORT_EXIT' | 'PARTIAL_SELL';
  price:    number;
  barIndex: number;
}

export interface BacktestStats {
  totalSignals: number;
  buySignals:   number;
  sellSignals:  number;
  totalTrades:  number;
  winTrades:    number;
  winRate:      number;
  totalReturn:  number;
  maxDrawdown:  number;
  avgReturn:    number;
  finalEquity:  number;
}

/** Ta4j AnalysisCriterion 전체 계산 결과 */
export interface BacktestAnalysis {
  initialCapital:      number;
  finalEquity:         number;
  totalReturnPct:      number;
  totalProfitLoss:     number;
  grossProfit:         number;
  grossLoss:           number;
  avgReturnPct:        number;
  profitLossRatio:     number;
  numberOfPositions:   number;
  numberOfWinning:     number;
  numberOfLosing:      number;
  numberOfBreakEven:   number;
  winRate:             number;
  maxDrawdownPct:      number;
  maxRunupPct:         number;
  sharpeRatio:         number;
  sortinoRatio:        number;
  calmarRatio:         number;
  valueAtRisk95:       number;
  expectedShortfall:   number;
  buyAndHoldReturnPct: number;
  vsBuyAndHold:        number;
}

export interface BacktestResponse {
  signals:  BacktestSignal[];
  stats:    BacktestStats;
  analysis?: BacktestAnalysis;
  resultId?: number;
}

/** DB 저장 이력 레코드 */
export interface BacktestResultRecord {
  id:           number;
  deviceId?:    string;
  strategyId?:  number;
  strategyName: string;
  symbol:       string;
  timeframe:    string;
  barCount:     number;
  fromTime:     number;
  toTime:       number;
  settingsJson: string;
  signalsJson:  string;
  analysisJson: string;
  totalReturn:  number;
  winRate:      number;
  totalTrades:  number;
  maxDrawdown:  number;
  sharpeRatio:  number;
  finalEquity:  number;
  createdAt:    string;
}

// ─────────────────────────────────────────────────────────────────────────────
// 백테스팅 설정 DTO
// ─────────────────────────────────────────────────────────────────────────────

export interface BacktestSettingsDto {
  id?:                  number;
  initialCapital:       number;
  commissionType:       'LINEAR' | 'ZERO';
  commissionRate:       number;
  slippageRate:         number;
  entryPriceType:       'CLOSE' | 'NEXT_OPEN';
  exitPriceType:        'CLOSE' | 'NEXT_OPEN';
  positionDirection:    'LONG' | 'SHORT' | 'BOTH';
  tradeSizeType:        'CAPITAL_PCT' | 'FIXED_AMOUNT';
  tradeSizeValue:       number;
  stopLossEnabled:      boolean;
  stopLossPct:          number;
  takeProfitEnabled:    boolean;
  takeProfitPct:        number;
  trailingStopEnabled:  boolean;
  trailingStopPct:      number;
  reentryWaitBars:      number;
  maxOpenTrades:        number;
  partialExitEnabled:   boolean;
  partialExitPct:       number;
  /** "LONG_ONLY" | "SIGNAL_ONLY" — 매도 시그널 포지션 종속성 모드 */
  positionMode?:        'LONG_ONLY' | 'SIGNAL_ONLY';
}

export const DEFAULT_BACKTEST_SETTINGS: BacktestSettingsDto = {
  initialCapital:      10_000_000,
  commissionType:      'LINEAR',
  commissionRate:      0.0015,
  slippageRate:        0.0005,
  entryPriceType:      'CLOSE',
  exitPriceType:       'CLOSE',
  positionDirection:   'LONG',
  tradeSizeType:       'CAPITAL_PCT',
  tradeSizeValue:      100,
  stopLossEnabled:     false,
  stopLossPct:         2,
  takeProfitEnabled:   false,
  takeProfitPct:       5,
  trailingStopEnabled: false,
  trailingStopPct:     2,
  reentryWaitBars:     0,
  maxOpenTrades:       1,
  partialExitEnabled:  false,
  partialExitPct:      50,
  positionMode:        'LONG_ONLY',
};

/** 백테스팅 설정 로드 */
export async function loadBacktestSettings(): Promise<BacktestSettingsDto> {
  return (await request<BacktestSettingsDto>('/backtest-settings')) ?? DEFAULT_BACKTEST_SETTINGS;
}

/** 백테스팅 설정 저장 */
export async function saveBacktestSettings(dto: BacktestSettingsDto): Promise<BacktestSettingsDto | null> {
  return request<BacktestSettingsDto>('/backtest-settings', {
    method: 'POST',
    body:   JSON.stringify(dto),
  });
}

export interface BacktestRequest {
  strategyId?:      number;
  buyCondition?:    unknown;
  sellCondition?:   unknown;
  bars:             { time: number; open: number; high: number; low: number; close: number; volume: number }[];
  timeframe:        string;
  indicatorParams?: Record<string, Record<string, unknown>>;
  settings?:        BacktestSettingsDto;
  symbol?:          string;
  strategyName?:    string;
  deviceId?:        string;
}

/** 백테스팅 실행 */
export async function runBacktest(req: BacktestRequest): Promise<BacktestResponse | null> {
  return request<BacktestResponse>('/backtesting/run', {
    method: 'POST',
    body:   JSON.stringify({ ...req, deviceId: getDeviceId() }),
  });
}

// ─────────────────────────────────────────────────────────────────────────────
// 백테스팅 결과 이력 API
// ─────────────────────────────────────────────────────────────────────────────

/** 결과 이력 목록 로드 */
export async function loadBacktestResults(): Promise<BacktestResultRecord[]> {
  return (await request<BacktestResultRecord[]>(
    `/backtest-results?deviceId=${encodeURIComponent(getDeviceId())}`
  )) ?? [];
}

/** 결과 단건 조회 */
export async function loadBacktestResult(id: number): Promise<BacktestResultRecord | null> {
  return request<BacktestResultRecord>(`/backtest-results/${id}`);
}

/** 결과 삭제 */
export async function deleteBacktestResult(id: number): Promise<void> {
  await request(`/backtest-results/${id}`, { method: 'DELETE' });
}

// ─────────────────────────────────────────────────────────────────────────────
// 실시간 전략 체크 설정 API
// ─────────────────────────────────────────────────────────────────────────────

export interface LiveStrategySettingsDto {
  market:        string;
  strategyId:    number | null;
  isLiveCheck:   boolean;
  /** "CANDLE_CLOSE" | "REALTIME_TICK" */
  executionType: string;
  /** "LONG_ONLY" | "SIGNAL_ONLY" — 매도 시그널 포지션 종속성 모드 */
  positionMode?: string;
  /** @deprecated 전략 DSL에서 시간봉을 자동 추출합니다 */
  candleType?: string;
  /** 전략 조건 DSL에 포함된 평가 시간봉 (응답 전용) */
  strategyCandleTypes?: string[];
}

/** 실시간 전략 체크 설정 로드 */
export async function loadLiveStrategySettings(
  market: string,
): Promise<LiveStrategySettingsDto> {
  return (await request<LiveStrategySettingsDto>(
    `/strategy/settings?market=${encodeURIComponent(market)}`,
  )) ?? { market, strategyId: null, isLiveCheck: false, executionType: 'CANDLE_CLOSE', positionMode: 'LONG_ONLY' };
}

/** 실시간 전략 체크 설정 저장 */
export async function saveLiveStrategySettings(
  dto: LiveStrategySettingsDto,
): Promise<LiveStrategySettingsDto | null> {
  return requestOrThrow<LiveStrategySettingsDto>('/strategy/settings', {
    method: 'PUT',
    body:   JSON.stringify(dto),
  });
}

/** 실시간 전략 STOMP 구독 목록 (종목 × 전략 DSL 시간봉) */
export function expandLiveStrategySubscriptions(
  list: LiveStrategySettingsDto[],
): { market: string; candleType: string }[] {
  return list.flatMap(s => {
    const types = s.strategyCandleTypes?.length ? s.strategyCandleTypes : ['1m'];
    return types.map(candleType => ({ market: s.market, candleType }));
  });
}

/** 현재 디바이스에서 실시간 체크 ON + 전략 지정된 종목 목록 */
export async function loadActiveLiveStrategySettings(): Promise<LiveStrategySettingsDto[]> {
  return (await request<LiveStrategySettingsDto[]>('/strategy/settings/active')) ?? [];
}

/** 관심종목 등 여러 마켓에 동일 실시간 전략 설정 일괄 적용 */
export async function saveLiveStrategySettingsBulk(
  markets: string[],
  template: Omit<LiveStrategySettingsDto, 'market'>,
): Promise<LiveStrategySettingsDto[]> {
  const list = (await request<LiveStrategySettingsDto[]>('/strategy/settings/bulk', {
    method: 'PUT',
    body:   JSON.stringify({ markets, template: { ...template, market: markets[0] ?? 'KRW-BTC' } }),
  })) ?? [];
  return list;
}
