/**
 * Session Service — 会话 CRUD + 消息持久化
 *
 * 管理会话的创建、查询、更新、删除，以及消息的存取。
 * 所有操作通过 better-sqlite3 同步执行。
 *
 * @see docs/MetonaAI-Desktop 架构与交互设计.html
 */

import { nanoid } from 'nanoid';
import type Database from 'better-sqlite3';
import log from 'electron-log';

// ===== 类型定义 =====

export interface SessionRow {
  id: string;
  title: string;
  created_at: number;
  updated_at: number;
  message_count: number;
  total_tokens: number;
  pinned: number;
  archived: number;
  metadata: string;
}

export interface MessageRow {
  id: string;
  session_id: string;
  role: string;
  // C-6 修复: content 允许 null — assistant 消息仅有 tool_calls 时为 null
  content: string | null;
  reasoning_content: string | null;
  tool_calls: string | null;
  tool_result: string | null;
  attachments: string | null;
  iteration: number | null;
  created_at: number;
}

export interface SessionInfo {
  id: string;
  title: string;
  createdAt: number;
  updatedAt: number;
  messageCount: number;
  totalTokens: number;
  pinned: boolean;
  archived: boolean;
}

export interface MessageInfo {
  id: string;
  role: string;
  // C-6 修复: content 允许 null — assistant 消息仅有 tool_calls 时为 null
  content: string | null;
  reasoningContent?: string;
  toolCalls?: unknown[];
  toolResult?: unknown;
  attachments?: unknown[];
  iteration?: number;
  timestamp: number;
}

// ===== 服务类 =====

export class SessionService {
  /**
   * 获取数据库实例
   */
  getDB(): Database.Database {
    return this.getDBFn();
  }

  constructor(private getDBFn: () => Database.Database) {}

  /**
   * 列出所有会话
   */
  list(options?: { archived?: boolean }): SessionInfo[] {
    const db = this.getDBFn();
    const archived = options?.archived ?? false;

    const rows = db.prepare(`
      SELECT * FROM sessions
      WHERE archived = ?
      ORDER BY pinned DESC, updated_at DESC
    `).all(archived ? 1 : 0) as SessionRow[];

    return rows.map((row) => this.toSessionInfo(row));
  }

  /**
   * 创建新会话
   */
  create(title?: string): SessionInfo {
    const db = this.getDBFn();
    const id = `s_${nanoid(12)}`;
    const now = Date.now();
    const sessionTitle = title ?? '新会话';

    db.prepare(`
      INSERT INTO sessions (id, title, created_at, updated_at)
      VALUES (?, ?, ?, ?)
    `).run(id, sessionTitle, now, now);

    log.info(`Session created: ${id} (${sessionTitle})`);

    return {
      id,
      title: sessionTitle,
      createdAt: now,
      updatedAt: now,
      messageCount: 0,
      totalTokens: 0,
      pinned: false,
      archived: false,
    };
  }

  /**
   * 重命名会话
   */
  rename(sessionId: string, title: string): boolean {
    const db = this.getDBFn();
    const result = db.prepare(`
      UPDATE sessions SET title = ?, updated_at = ? WHERE id = ?
    `).run(title, Date.now(), sessionId);

    if (result.changes > 0) {
      log.info(`Session renamed: ${sessionId} → ${title}`);
      return true;
    }
    return false;
  }

  /**
   * 删除会话（级联删除消息）
   */
  delete(sessionId: string): boolean {
    const db = this.getDBFn();
    const result = db.prepare('DELETE FROM sessions WHERE id = ?').run(sessionId);

    if (result.changes > 0) {
      log.info(`Session deleted: ${sessionId}`);
      return true;
    }
    return false;
  }

  /**
   * 置顶/取消置顶
   */
  pin(sessionId: string, pinned: boolean): boolean {
    const db = this.getDBFn();
    const result = db.prepare(`
      UPDATE sessions SET pinned = ?, updated_at = ? WHERE id = ?
    `).run(pinned ? 1 : 0, Date.now(), sessionId);

    return result.changes > 0;
  }

  /**
   * 归档/取消归档
   */
  archive(sessionId: string, archived: boolean): boolean {
    const db = this.getDBFn();
    const result = db.prepare(`
      UPDATE sessions SET archived = ?, updated_at = ? WHERE id = ?
    `).run(archived ? 1 : 0, Date.now(), sessionId);

    return result.changes > 0;
  }

  /**
   * 获取会话消息列表
   *
   * #44 修复: 添加 limit/offset 参数支持分页，避免超长会话一次性加载导致 OOM
   *   默认不限制（limit=0），保持向后兼容；调用者可传 limit 限制返回条数
   */
  getMessages(
    sessionId: string,
    options: { limit?: number; offset?: number } = {},
  ): MessageInfo[] {
    const db = this.getDBFn();
    const { limit = 0, offset = 0 } = options;

    const rows = (limit > 0
      ? db
          .prepare(
            'SELECT * FROM messages WHERE session_id = ? ORDER BY created_at ASC LIMIT ? OFFSET ?',
          )
          .all(sessionId, limit, offset)
      : db
          .prepare('SELECT * FROM messages WHERE session_id = ? ORDER BY created_at ASC')
          .all(sessionId)) as MessageRow[];

    return rows.map((row) => this.toMessageInfo(row));
  }

  /**
   * 保存一条消息
   */
  saveMessage(params: {
    sessionId: string;
    role: string;
    // C-6 修复: content 允许 null — assistant 消息仅有 tool_calls 时为 null
    content: string | null;
    reasoningContent?: string;
    toolCalls?: unknown[];
    toolResult?: unknown;
    attachments?: unknown[];
    iteration?: number;
  }): MessageInfo {
    const db = this.getDBFn();
    const id = `msg_${nanoid(12)}`;
    const now = Date.now();

    // #34 修复: 包裹事务保证 INSERT messages 和 UPDATE sessions 原子执行
    //   message_count = message_count + 1 已是原子 SQL 表达式（避免读-改-写竞态）
    //   事务进一步保证消息插入和计数更新要么全部成功，要么全部回滚
    const saveMessageTxn = db.transaction(() => {
      db.prepare(`
        INSERT INTO messages (id, session_id, role, content, reasoning_content, tool_calls, tool_result, attachments, iteration, created_at)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      `).run(
        id,
        params.sessionId,
        params.role,
        params.content,
        params.reasoningContent ?? null,
        params.toolCalls ? JSON.stringify(params.toolCalls) : null,
        params.toolResult ? JSON.stringify(params.toolResult) : null,
        params.attachments ? JSON.stringify(params.attachments) : null,
        params.iteration ?? null,
        now,
      );

      // 更新会话的 updated_at 和 message_count
      db.prepare(`
        UPDATE sessions
        SET updated_at = ?, message_count = message_count + 1
        WHERE id = ?
      `).run(now, params.sessionId);
    });
    saveMessageTxn();

    return {
      id,
      role: params.role,
      content: params.content,
      reasoningContent: params.reasoningContent,
      toolCalls: params.toolCalls,
      toolResult: params.toolResult,
      attachments: params.attachments,
      iteration: params.iteration,
      timestamp: now,
    };
  }

  /**
   * 更新会话 Token 统计
   */
  updateTokenUsage(sessionId: string, tokens: number): void {
    const db = this.getDBFn();
    db.prepare(`
      UPDATE sessions SET total_tokens = total_tokens + ? WHERE id = ?
    `).run(tokens, sessionId);
  }

  /**
   * 删除一条消息
   */
  deleteMessage(messageId: string): boolean {
    const db = this.getDBFn();
    const result = db.prepare('DELETE FROM messages WHERE id = ?').run(messageId);
    return result.changes > 0;
  }

  /**
   * 清空会话所有消息
   */
  clearMessages(sessionId: string): boolean {
    const db = this.getDBFn();
    const result = db.prepare('DELETE FROM messages WHERE session_id = ?').run(sessionId);
    db.prepare('UPDATE sessions SET message_count = 0, updated_at = ? WHERE id = ?').run(Date.now(), sessionId);
    return result.changes > 0;
  }

  /**
   * 保存会话的 trace 步骤和 token 用量（存入 metadata JSON）
   */
  saveTraceData(sessionId: string, data: { traceSteps: unknown[]; tokenUsage: unknown }): void {
    const db = this.getDBFn();
    const metadata = JSON.stringify({ traceSteps: data.traceSteps, tokenUsage: data.tokenUsage });
    db.prepare(`
      UPDATE sessions SET metadata = ?, updated_at = ? WHERE id = ?
    `).run(metadata, Date.now(), sessionId);
  }

  /**
   * 加载会话的 trace 步骤和 token 用量
   */
  getTraceData(sessionId: string): { traceSteps: unknown[]; tokenUsage: unknown } | null {
    const db = this.getDBFn();
    const row = db.prepare('SELECT metadata FROM sessions WHERE id = ?').get(sessionId) as { metadata: string } | undefined;
    if (!row?.metadata) return null;
    try {
      const data = JSON.parse(row.metadata);
      if (data.traceSteps || data.tokenUsage) return data;
      return null;
    } catch {
      return null;
    }
  }

  // ===== 私有转换方法 =====

  private toSessionInfo(row: SessionRow): SessionInfo {
    return {
      id: row.id,
      title: row.title,
      createdAt: row.created_at,
      updatedAt: row.updated_at,
      messageCount: row.message_count,
      totalTokens: row.total_tokens,
      pinned: row.pinned === 1,
      archived: row.archived === 1,
    };
  }

  private toMessageInfo(row: MessageRow): MessageInfo {
    // M-55 修复: 运行时收窄，防止数据库被篡改或老版本数据格式不一致导致下游 .map/.length 崩溃
    const parsedToolCalls = row.tool_calls ? this.safeJsonParse(row.tool_calls) : undefined;
    const parsedAttachments = row.attachments ? this.safeJsonParse(row.attachments) : undefined;
    return {
      id: row.id,
      role: row.role,
      content: row.content,
      reasoningContent: row.reasoning_content ?? undefined,
      toolCalls: Array.isArray(parsedToolCalls) ? parsedToolCalls : undefined,
      toolResult: row.tool_result ? this.safeJsonParse(row.tool_result) : undefined,
      attachments: Array.isArray(parsedAttachments) ? parsedAttachments : undefined,
      iteration: row.iteration ?? undefined,
      timestamp: row.created_at,
    };
  }

  /**
   * 安全 JSON 解析：解析失败时返回 undefined 而非抛出异常
   */
  private safeJsonParse(json: string): unknown {
    try {
      return JSON.parse(json);
    } catch {
      return undefined;
    }
  }
}
