import http from './http'

/**
 * 「日志管理」前端胶水：对应 MiGu.Server `LogsController`（`/api/logs/*`）。
 *
 * 数据是 SimpleLite 内核 `Diagnosis.Post / Diagnosis.Log` 写到工作目录 `log/{日期}/xxx.log` 的落盘日志
 * （俗称 DLog）。后端直接读文件并解析为结构化条目，提供：概览 / 文件列表 / 条目分页 /
 * 「按标签合订」/ 原文 / 下载。
 *
 * 与 config.ts 一致直接返回数据对象（非 reflection 的 success/data 信封）；错误由 http.ts
 * 拦截器翻译成中文。VITE_USE_MOCK=true 时返回内置样例，便于无后端调试界面。
 */

const MOCK = import.meta.env.VITE_USE_MOCK === 'true'
const API_BASE = (import.meta.env.VITE_API_BASE as string | undefined) ?? '/api'

/** SimpleLite projection 投影 API 统一信封（同 reflection.ts）。 */
interface SlEnvelope<T> { success: boolean; code: number; data: T | null; message: string }

/** 经 YARP 反代到 SimpleLite EmbedIO 的诊断投影端点（/api/sl/projection/diagnosis）。 */
const SL_DIAG = '/sl/projection/diagnosis'

export interface LogEntry {
  lineNo: number
  /** ISO 时间；无法解析时间戳的续行/异常行为 null。 */
  time: string | null
  prefix: string
  /** 空串表示无标签（滚动记录）。 */
  tag: string
  content: string
}

export interface LogFile {
  /** 相对日志根的路径，如 `2026-06-01/20260601-12Q(30).log`，作为其它接口的 file 参数。 */
  rel: string
  name: string
  /** 一级目录（通常是日期），无子目录时为「(根目录)」。 */
  day: string
  dir: string
  bytes: number
  mtime: string
}

export interface LogDayStat {
  day: string
  files: number
  bytes: number
}

export interface LogOverview {
  exists: boolean
  root: string | null
  workingDirectory: string | null
  totalFiles?: number
  totalBytes?: number
  latestFileTime?: string | null
  days?: LogDayStat[]
  message?: string
}

export interface LogFilesResult {
  root: string
  total: number
  returned: number
  files: LogFile[]
}

export interface LogEntriesResult {
  file: string
  bytes: number
  scannedLines: number
  truncated: boolean
  total: number
  offset: number
  limit: number
  order: string
  entries: LogEntry[]
}

/** 合订本「一册」：同一标签聚合后的视图。 */
export interface LogBook {
  tag: string
  count: number
  firstTime?: string | null
  lastTime?: string | null
  latest?: string
  latestTime?: string | null
  entries: LogEntry[]
}

export interface LogDigest {
  source: 'file' | 'day' | string
  target: string
  files: number
  truncated: boolean
  tagCount: number
  untaggedCount: number
  books: LogBook[]
  untagged: LogBook
}

export interface EntriesQuery {
  file: string
  keyword?: string
  tag?: string
  onlyTagged?: boolean
  order?: 'asc' | 'desc'
  limit?: number
  offset?: number
}

export interface DigestQuery {
  file?: string
  day?: string
  keyword?: string
  maxPerTag?: number
}

/** 实时诊断单条：对应 SimpleLite 内核 Diagnosis 的内存态 Post/Toast。 */
export interface LiveDiagItem {
  index: number
  time: string
  /** 空串=无标签（滚动记录）；非空=按标签合订（同标签仅留最新一条）。 */
  tag: string
  tagged: boolean
  content: string
}

export interface LiveDiagnosis {
  serverTime: string
  total: number
  taggedCount: number
  untaggedCount: number
  items: LiveDiagItem[]
}

/** 目录浏览：子文件夹。 */
export interface BrowseDir {
  name: string
  rel: string
  mtime: string
  dirCount: number
  fileCount: number
}

/** 目录浏览：文件。 */
export interface BrowseFile {
  name: string
  rel: string
  bytes: number
  mtime: string
  isLog: boolean
}

export interface BrowseResult {
  root: string
  exists: boolean
  /** 当前相对路径（""=日志根）。 */
  path: string
  /** 上一级相对路径；位于根时为 null。 */
  parent: string | null
  dirCount: number
  fileCount: number
  dirs: BrowseDir[]
  files: BrowseFile[]
  message?: string
}

// ───────────────────────────────────────────────── 日志分析器 ──

/** 标签分布一项。 */
export interface AnalyzeTag { tag: string; count: number; percent: number }

/** 日志量直方图：稀疏桶（仅含有数据的时刻）+ Top 标签拆分（与 buckets 等长对齐）。 */
export interface AnalyzeVolume {
  granularity: string
  buckets: string[]
  total: number[]
  topTags: Array<{ tag: string; counts: number[] }>
}

/** 识别出的数值字段统计。 */
export interface AnalyzeField {
  name: string
  samples: number
  min: number
  max: number
  avg: number
  last: number
}

/** 选定字段的时序点序列。 */
export interface AnalyzeSeries {
  field: string
  tag: string
  count: number
  points: Array<{ t: string; v: number }>
}

export interface AnalyzeResult {
  source: string
  target: string
  files: number
  truncated: boolean
  total: number
  timeRange: { start: string | null; end: string | null }
  granularity: string
  tags: AnalyzeTag[]
  volume: AnalyzeVolume
  fields: AnalyzeField[]
  series: AnalyzeSeries | null
}

export interface AnalyzeQuery {
  file?: string
  day?: string
  granularity?: 'second' | 'minute' | 'hour'
  tag?: string
  keyword?: string
  field?: string
}

// ───────────────────────────────────────────────────────────── mock ──

function mockOverview(): LogOverview {
  return {
    exists: true,
    root: 'E:\\...\\SimpleLite\\bin\\Debug\\log',
    workingDirectory: 'E:\\...\\SimpleLite\\bin\\Debug',
    totalFiles: 3,
    totalBytes: 5_233_649,
    latestFileTime: new Date().toISOString(),
    days: [{ day: '2026-06-01', files: 3, bytes: 5_233_649 }]
  }
}

function mockFiles(): LogFilesResult {
  const files: LogFile[] = [
    { rel: '2026-06-01/20260601-12Q(30).log', name: '20260601-12Q(30).log', day: '2026-06-01', dir: '2026-06-01', bytes: 5_223_512, mtime: '2026-06-01T12:44:59' },
    { rel: '2026-06-01/20260601-12Q(15).log', name: '20260601-12Q(15).log', day: '2026-06-01', dir: '2026-06-01', bytes: 1188, mtime: '2026-06-01T12:21:44' },
    { rel: '2026-06-01/20260601-08Q(45).log', name: '20260601-08Q(45).log', day: '2026-06-01', dir: '2026-06-01', bytes: 949, mtime: '2026-06-01T08:47:20' }
  ]
  return { root: 'mock', total: files.length, returned: files.length, files }
}

function mockEntries(q: EntriesQuery): LogEntriesResult {
  const base: LogEntry[] = [
    { lineNo: 1, time: '2026-06-01T12:21:40.120', prefix: '', tag: 'Persistence', content: 'worker started, schema=1.2' },
    { lineNo: 2, time: '2026-06-01T12:21:41.330', prefix: '', tag: '', content: 'loaded 12 sites, 18 tracks' },
    { lineNo: 3, time: '2026-06-01T12:21:42.880', prefix: '', tag: 'Dispatch', content: 'dispatch loop 50Hz online' },
    { lineNo: 4, time: '2026-06-01T12:21:44.010', prefix: '', tag: 'Persistence', content: 'flush 4 entities ok' },
    { lineNo: 5, time: '2026-06-01T12:21:45.220', prefix: '', tag: 'UI-Error', content: 'panel repaint skipped (terminal closing)' }
  ]
  let list = base
  if (q.onlyTagged) list = list.filter((e) => e.tag)
  if (q.tag) list = list.filter((e) => e.tag.includes(q.tag!))
  if (q.keyword) list = list.filter((e) => e.content.includes(q.keyword!) || e.tag.includes(q.keyword!))
  if (q.order !== 'asc') list = [...list].reverse()
  return { file: q.file, bytes: 5_223_512, scannedLines: base.length, truncated: false, total: list.length, offset: 0, limit: 300, order: q.order ?? 'desc', entries: list }
}

function mockDigest(): LogDigest {
  const mk = (tag: string, n: number, latest: string): LogBook => ({
    tag, count: n, firstTime: '2026-06-01T12:00:00', lastTime: '2026-06-01T12:44:00',
    latest, latestTime: '2026-06-01T12:44:00',
    entries: Array.from({ length: Math.min(n, 3) }, (_, i) => ({
      lineNo: i + 1, time: '2026-06-01T12:4' + i + ':00.000', prefix: '', tag, content: `${latest} #${i + 1}`
    }))
  })
  return {
    source: 'file', target: 'mock', files: 1, truncated: false, tagCount: 3, untaggedCount: 42,
    books: [mk('Persistence', 128, 'flush ok'), mk('Dispatch', 64, 'loop tick'), mk('UI-Error', 3, 'repaint skipped')],
    untagged: { tag: '', count: 42, latest: 'misc rolling line', entries: [
      { lineNo: 2, time: '2026-06-01T12:21:41.330', prefix: '', tag: '', content: 'loaded 12 sites, 18 tracks' }
    ] }
  }
}

// ─────────────────────────────────────────────────────────── client ──

function mockLiveDiagnosis(): LiveDiagnosis {
  const now = new Date()
  const iso = (sec: number) => new Date(now.getTime() - sec * 1000).toISOString().slice(0, 23)
  const items: LiveDiagItem[] = [
    { index: 0, time: iso(2), tag: 'Persistence', tagged: true, content: 'flush 4 entities ok' },
    { index: 1, time: iso(4), tag: 'Dispatch', tagged: true, content: 'dispatch loop 50Hz online' },
    { index: 2, time: iso(6), tag: 'InternalAuth', tagged: true, content: '仅放行本机回环（无 internal token 配置）' },
    { index: 3, time: iso(1), tag: '', tagged: false, content: '[SimpleLite] Projection API listening on http://127.0.0.1:8222/projection/' },
    { index: 4, time: iso(8), tag: '', tagged: false, content: 'loaded 12 sites, 18 tracks' }
  ]
  const taggedCount = items.filter((i) => i.tagged).length
  return { serverTime: now.toISOString().slice(0, 23), total: items.length, taggedCount, untaggedCount: items.length - taggedCount, items }
}

function mockBrowse(path?: string): BrowseResult {
  if (!path) {
    return {
      root: 'mock-log', exists: true, path: '', parent: null, dirCount: 1, fileCount: 0,
      dirs: [{ name: '2026-06-01', rel: '2026-06-01', mtime: '2026-06-01T12:44:59', dirCount: 0, fileCount: 3 }],
      files: []
    }
  }
  return {
    root: 'mock-log', exists: true, path, parent: '', dirCount: 0, fileCount: 3,
    dirs: [],
    files: [
      { name: '20260601-12Q(30).log', rel: `${path}/20260601-12Q(30).log`, bytes: 5_223_512, mtime: '2026-06-01T12:44:59', isLog: true },
      { name: '20260601-12Q(15).log', rel: `${path}/20260601-12Q(15).log`, bytes: 1188, mtime: '2026-06-01T12:21:44', isLog: true },
      { name: '20260601-08Q(45).log', rel: `${path}/20260601-08Q(45).log`, bytes: 949, mtime: '2026-06-01T08:47:20', isLog: true }
    ]
  }
}

function mockAnalyze(q: AnalyzeQuery): AnalyzeResult {
  const now = Date.now()
  const gran = q.granularity ?? 'minute'
  const step = gran === 'second' ? 1000 : gran === 'hour' ? 3_600_000 : 60_000
  const n = 30
  const buckets = Array.from({ length: n }, (_, i) => new Date(now - (n - 1 - i) * step).toISOString())
  const wave = (amp: number, base: number, ph: number) =>
    buckets.map((_, i) => Math.max(0, Math.round(base + amp * Math.sin(i / 3 + ph))))
  const fields: AnalyzeField[] = [
    { name: 'cost', samples: 420, min: 2, max: 88, avg: 18.4, last: 21 },
    { name: 'queue', samples: 380, min: 0, max: 32, avg: 6.1, last: 4 },
    { name: 'speed', samples: 300, min: 0, max: 1.5, avg: 0.7, last: 0.9 }
  ]
  const series: AnalyzeSeries | null = q.field
    ? {
      field: q.field, tag: q.tag ?? '', count: n,
      points: buckets.map((t, i) => ({ t, v: Math.round((18 + 10 * Math.sin(i / 2)) * 10) / 10 }))
    }
    : null
  return {
    source: q.file ? 'file' : 'day', target: q.file ?? q.day ?? 'mock', files: 1, truncated: false,
    total: 1280,
    timeRange: { start: buckets[0], end: buckets[n - 1] },
    granularity: gran,
    tags: [
      { tag: 'Dispatch', count: 520, percent: 40.6 },
      { tag: 'Persistence', count: 360, percent: 28.1 },
      { tag: '交管#3', count: 210, percent: 16.4 },
      { tag: 'UI-Error', count: 90, percent: 7.0 },
      { tag: '', count: 100, percent: 7.8 }
    ],
    volume: {
      granularity: gran, buckets,
      total: wave(20, 40, 0),
      topTags: [
        { tag: 'Dispatch', counts: wave(10, 18, 0) },
        { tag: 'Persistence', counts: wave(8, 12, 1) },
        { tag: '交管#3', counts: wave(6, 7, 2) }
      ]
    },
    fields,
    series
  }
}

export const logsApi = {
  overview: (): Promise<LogOverview> => MOCK
    ? Promise.resolve(mockOverview())
    : http.get<LogOverview>('/logs/overview').then((r) => r.data),

  files: (params?: { day?: string; keyword?: string; limit?: number }): Promise<LogFilesResult> => MOCK
    ? Promise.resolve(mockFiles())
    : http.get<LogFilesResult>('/logs/files', { params }).then((r) => r.data),

  entries: (q: EntriesQuery): Promise<LogEntriesResult> => MOCK
    ? Promise.resolve(mockEntries(q))
    : http.get<LogEntriesResult>('/logs/entries', { params: q }).then((r) => r.data),

  digest: (q: DigestQuery): Promise<LogDigest> => MOCK
    ? Promise.resolve(mockDigest())
    : http.get<LogDigest>('/logs/digest', { params: q }).then((r) => r.data),

  /** 实时内存诊断（SimpleLite Diagnosis.GetAllDiagnosis，经 YARP 反代）。需 SimpleLite 在运行，否则 502。 */
  liveDiagnosis: (): Promise<LiveDiagnosis> => MOCK
    ? Promise.resolve(mockLiveDiagnosis())
    : http.get<SlEnvelope<LiveDiagnosis>>(`${SL_DIAG}/all`).then((r) => {
      if (!r.data?.success || !r.data.data) throw new Error(r.data?.message ?? '获取实时诊断失败')
      return r.data.data
    }),

  /** 目录浏览：列出 log/ 下某相对目录的子文件夹 + 文件（path 为空=根）。 */
  browse: (path?: string): Promise<BrowseResult> => MOCK
    ? Promise.resolve(mockBrowse(path))
    : http.get<BrowseResult>('/logs/browse', { params: path ? { path } : undefined }).then((r) => r.data),

  /** 日志分析：标签分布 / 时间直方图 / 数值字段识别 / 选定字段时序。file 或 day 二选一。 */
  analyze: (q: AnalyzeQuery): Promise<AnalyzeResult> => MOCK
    ? Promise.resolve(mockAnalyze(q))
    : http.get<AnalyzeResult>('/logs/analyze', { params: q }).then((r) => r.data),

  /** 取文件尾部 N 行原文（text/plain）。 */
  raw: (file: string, tail = 2000): Promise<string> => MOCK
    ? Promise.resolve('[2026/06/01-12:21:40.120] >Persistence: worker started\n[2026/06/01-12:21:41.330] >/: loaded 12 sites')
    : http.get('/logs/raw', { params: { file, tail }, responseType: 'text' }).then((r) => r.data as string),

  /** 浏览器直链下载（GET，靠 httpOnly Cookie 鉴权）。 */
  downloadUrl: (file: string): string =>
    `${API_BASE}/logs/download?file=${encodeURIComponent(file)}`
}
