/**
 * CLI Executor State
 * Conversation history + execution record storage (SQLite-backed)
 */

import type { HistoryIndexEntry } from './cli-history-store.js';
import { StoragePaths, ensureStorageDir } from '../config/storage-paths.js';
import type { CliOutputUnit } from './cli-output-converter.js';
import * as fs from 'fs';
import * as path from 'path';

// Lazy-loaded SQLite store module
let sqliteStoreModule: typeof import('./cli-history-store.js') | null = null;

/**
 * Get or initialize SQLite store (async)
 */
export async function getSqliteStore(baseDir: string) {
  if (!sqliteStoreModule) {
    sqliteStoreModule = await import('./cli-history-store.js');
  }
  return sqliteStoreModule.getHistoryStore(baseDir);
}

/**
 * Get SQLite store (sync - uses cached module)
 */
function getSqliteStoreSync(baseDir: string) {
  if (!sqliteStoreModule) {
    throw new Error('SQLite store not initialized. Call an async function first.');
  }
  return sqliteStoreModule.getHistoryStore(baseDir);
}

// Execution category types
export type ExecutionCategory = 'user' | 'internal' | 'insight';

// Single turn in a conversation
export interface ConversationTurn {
  turn: number;
  timestamp: string;
  prompt: string;
  duration_ms: number;
  status: 'success' | 'error' | 'timeout';
  exit_code: number | null;
  output: {
    stdout: string;
    stderr: string;
    truncated: boolean;
    cached?: boolean;
    stdout_full?: string;
    stderr_full?: string;
    structured?: CliOutputUnit[];  // Structured IR sequence for advanced parsing
  };
}

// Multi-turn conversation record
export interface ConversationRecord {
  id: string;
  created_at: string;
  updated_at: string;
  tool: string;
  model: string;
  mode: string;
  category: ExecutionCategory; // user | internal | insight
  total_duration_ms: number;
  turn_count: number;
  latest_status: 'success' | 'error' | 'timeout';
  turns: ConversationTurn[];
  parent_execution_id?: string; // For fork/retry scenarios
}

// Legacy single execution record (for backward compatibility)
export interface ExecutionRecord {
  id: string;
  timestamp: string;
  tool: string;
  model: string;
  mode: string;
  prompt: string;
  status: 'success' | 'error' | 'timeout';
  exit_code: number | null;
  duration_ms: number;
  output: {
    stdout: string;
    stderr: string;
    truncated: boolean;
  };
  parsedOutput?: string;  // Extracted clean text from structured output units
  finalOutput?: string;  // Agent message only (for --final flag)
}

interface HistoryIndex {
  version: number;
  total_executions: number;
  executions: {
    id: string;
    timestamp: string;      // created_at for conversations
    updated_at?: string;    // last update time
    tool: string;
    status: string;
    duration_ms: number;
    turn_count?: number;    // number of turns in conversation
    prompt_preview: string;
  }[];
}

export interface ExecutionOutput {
  success: boolean;
  execution: ExecutionRecord;
  conversation: ConversationRecord;  // Full conversation record
  stdout: string;
  stderr: string;
  parsedOutput?: string;  // Extracted text from stream JSON response
  finalOutput?: string;  // Agent message only (for --final flag)
}

/**
 * Ensure history directory exists (uses centralized storage)
 */
export function ensureHistoryDir(baseDir: string): string {
  const paths = StoragePaths.project(baseDir);
  ensureStorageDir(paths.cliHistory);
  return paths.cliHistory;
}

/**
 * Save conversation to SQLite
 * @param baseDir - Project base directory (NOT historyDir)
 */
async function saveConversationAsync(baseDir: string, conversation: ConversationRecord): Promise<void> {
  const store = await getSqliteStore(baseDir);
  await store.saveConversation(conversation);
}

/**
 * Sync wrapper for saveConversation (uses cached SQLite module)
 * @param baseDir - Project base directory (NOT historyDir)
 */
export function saveConversation(baseDir: string, conversation: ConversationRecord): void {
  try {
    const store = getSqliteStoreSync(baseDir);
    // Fire and forget - don't block on async save in sync context
    store.saveConversation(conversation).catch(err => {
      console.error('[CLI Executor] Failed to save conversation:', err.message);
    });
  } catch (err) {
    // If sync not available, queue for async save
    saveConversationAsync(baseDir, conversation).catch(err => {
      console.error('[CLI Executor] Failed to save conversation:', err.message);
    });
  }
}

/**
 * Load existing conversation by ID from SQLite
 * @param baseDir - Project base directory (NOT historyDir)
 */
async function loadConversationAsync(baseDir: string, conversationId: string): Promise<ConversationRecord | null> {
  const store = await getSqliteStore(baseDir);
  return store.getConversation(conversationId);
}

/**
 * Sync wrapper for loadConversation (uses cached SQLite module)
 * @param baseDir - Project base directory (NOT historyDir)
 */
export function loadConversation(baseDir: string, conversationId: string): ConversationRecord | null {
  try {
    const store = getSqliteStoreSync(baseDir);
    return store.getConversation(conversationId);
  } catch {
    // SQLite not initialized yet, return null
    return null;
  }
}

/**
 * Convert legacy ExecutionRecord to ConversationRecord
 */
export function convertToConversation(record: ExecutionRecord): ConversationRecord {
  return {
    id: record.id,
    created_at: record.timestamp,
    updated_at: record.timestamp,
    tool: record.tool,
    model: record.model,
    mode: record.mode,
    category: 'user', // Legacy records default to user category
    total_duration_ms: record.duration_ms,
    turn_count: 1,
    latest_status: record.status,
    turns: [{
      turn: 1,
      timestamp: record.timestamp,
      prompt: record.prompt,
      duration_ms: record.duration_ms,
      status: record.status,
      exit_code: record.exit_code,
      output: record.output
    }]
  };
}

/**
 * Get execution history from SQLite (centralized storage)
 */
export async function getExecutionHistoryAsync(baseDir: string, options: {
  limit?: number;
  tool?: string | null;
  status?: string | null;
  category?: ExecutionCategory | null;
  search?: string | null;
  recursive?: boolean;
} = {}): Promise<{
  total: number;
  count: number;
  executions: (HistoryIndex['executions'][0] & { sourceDir?: string })[];
}> {
  const { limit = 50, tool = null, status = null, category = null, search = null, recursive = false } = options;

  // Recursive mode: aggregate data from parent and all child projects
  if (recursive) {
    const { scanChildProjectsAsync } = await import('../config/storage-paths.js');
    const childProjects = await scanChildProjectsAsync(baseDir);

    let allExecutions: (HistoryIndex['executions'][0] & { sourceDir?: string })[] = [];
    let totalCount = 0;

    // Query parent project - apply limit at source to reduce memory footprint
    try {
      const parentStore = await getSqliteStore(baseDir);
      const parentResult = parentStore.getHistory({ limit, tool, status, category, search });
      totalCount += parentResult.total;

      for (const exec of parentResult.executions) {
        allExecutions.push({ ...exec, sourceDir: baseDir });
      }
    } catch (error) {
      if (process.env.DEBUG) {
        console.error(`[CLI History] Failed to query parent project ${baseDir}:`, error);
      }
    }

    // Query all child projects - apply limit to each child
    for (const child of childProjects) {
      try {
        const childStore = await getSqliteStore(child.projectPath);
        const childResult = childStore.getHistory({ limit, tool, status, category, search });
        totalCount += childResult.total;

        for (const exec of childResult.executions) {
          allExecutions.push({
            ...exec,
            sourceDir: child.relativePath // Show relative path for clarity
          });
        }
      } catch (error) {
        if (process.env.DEBUG) {
          console.error(`[CLI History] Failed to query child project ${child.projectPath}:`, error);
        }
      }
    }

    // Sort by timestamp (newest first) and apply limit
    allExecutions.sort((a, b) => Number(b.timestamp) - Number(a.timestamp));
    const limitedExecutions = allExecutions.slice(0, limit);

    return {
      total: totalCount,
      count: limitedExecutions.length,
      executions: limitedExecutions
    };
  }

  // Non-recursive mode: only query current project
  const store = await getSqliteStore(baseDir);
  return store.getHistory({ limit, tool, status, category, search });
}

/**
 * Get execution history (sync version - uses cached SQLite module)
 */
export function getExecutionHistory(baseDir: string, options: {
  limit?: number;
  tool?: string | null;
  status?: string | null;
  recursive?: boolean;
} = {}): {
  total: number;
  count: number;
  executions: (HistoryIndex['executions'][0] & { sourceDir?: string })[];
} {
  const { limit = 50, tool = null, status = null, recursive = false } = options;

  try {
    if (recursive) {
      const { scanChildProjects } = require('../config/storage-paths.js');
      const childProjects = scanChildProjects(baseDir);

      let allExecutions: (HistoryIndex['executions'][0] & { sourceDir?: string })[] = [];
      let totalCount = 0;

      // Query parent project - apply limit at source
      try {
        const parentStore = getSqliteStoreSync(baseDir);
        const parentResult = parentStore.getHistory({ limit, tool, status });
        totalCount += parentResult.total;

        for (const exec of parentResult.executions) {
          allExecutions.push({ ...exec, sourceDir: baseDir });
        }
      } catch (error) {
        if (process.env.DEBUG) {
          console.error(`[CLI History] Failed to query parent project ${baseDir}:`, error);
        }
      }

      // Query all child projects - apply limit to each child
      for (const child of childProjects) {
        try {
          const childStore = getSqliteStoreSync(child.projectPath);
          const childResult = childStore.getHistory({ limit, tool, status });
          totalCount += childResult.total;

          for (const exec of childResult.executions) {
            allExecutions.push({
              ...exec,
              sourceDir: child.relativePath // Show relative path for clarity
            });
          }
        } catch (error) {
          if (process.env.DEBUG) {
            console.error(`[CLI History] Failed to query child project ${child.projectPath}:`, error);
          }
        }
      }

      // Sort by timestamp (newest first) and apply limit
      allExecutions.sort((a, b) => Number(b.timestamp) - Number(a.timestamp));
      const limitedExecutions = allExecutions.slice(0, limit);

      return {
        total: totalCount,
        count: limitedExecutions.length,
        executions: limitedExecutions
      };
    }

    const store = getSqliteStoreSync(baseDir);
    return store.getHistory({ limit, tool, status });
  } catch {
    // SQLite not initialized yet, return empty
    return { total: 0, count: 0, executions: [] };
  }
}

/**
 * Get conversation detail by ID
 */
export function getConversationDetail(baseDir: string, conversationId: string): ConversationRecord | null {
  // Pass baseDir directly - loadConversation will resolve the correct storage path
  return loadConversation(baseDir, conversationId);
}

/**
 * Get conversation detail with native session mapping info
 */
export function getConversationDetailWithNativeInfo(baseDir: string, conversationId: string) {
  try {
    const store = getSqliteStoreSync(baseDir);
    return store.getConversationWithNativeInfo(conversationId);
  } catch {
    // SQLite not initialized, return null
    return null;
  }
}

/**
 * Get execution detail by ID (legacy, returns ExecutionRecord for backward compatibility)
 */
export function getExecutionDetail(baseDir: string, executionId: string): ExecutionRecord | null {
  const conversation = getConversationDetail(baseDir, executionId);
  if (!conversation) return null;

  // Convert to legacy ExecutionRecord format (using latest turn)
  const latestTurn = conversation.turns[conversation.turns.length - 1];
  return {
    id: conversation.id,
    timestamp: conversation.created_at,
    tool: conversation.tool,
    model: conversation.model,
    mode: conversation.mode,
    prompt: latestTurn.prompt,
    status: conversation.latest_status,
    exit_code: latestTurn.exit_code,
    duration_ms: conversation.total_duration_ms,
    output: latestTurn.output
  };
}

/**
 * Delete execution by ID (async version)
 */
export async function deleteExecutionAsync(baseDir: string, executionId: string): Promise<{ success: boolean; error?: string }> {
  const store = await getSqliteStore(baseDir);
  return await store.deleteConversation(executionId);
}

/**
 * Delete execution by ID (sync version - uses cached SQLite module)
 */
export function deleteExecution(baseDir: string, executionId: string): { success: boolean; error?: string } {
  try {
    const store = getSqliteStoreSync(baseDir);
    // Fire and forget - don't block on async delete in sync context
    store.deleteConversation(executionId).catch(err => {
      console.error('[CLI Executor] Failed to delete execution:', err.message);
    });
    return { success: true }; // Optimistic response
  } catch {
    return { success: false, error: 'SQLite store not initialized' };
  }
}

/**
 * Batch delete executions (async)
 */
export async function batchDeleteExecutionsAsync(baseDir: string, ids: string[]): Promise<{
  success: boolean;
  deleted: number;
  total: number;
  errors?: string[];
}> {
  const store = await getSqliteStore(baseDir);
  const result = await store.batchDelete(ids);
  return { ...result, total: ids.length };
}

/**
 * Get latest execution for a specific tool
 */
export function getLatestExecution(baseDir: string, tool?: string): ExecutionRecord | null {
  const history = getExecutionHistory(baseDir, { limit: 1, tool: tool || null });
  if (history.executions.length === 0) {
    return null;
  }
  return getExecutionDetail(baseDir, history.executions[0].id);
}

// ========== Native Session Content Functions ==========

/**
 * Get native session content by CCW ID
 * Parses the native session file and returns full conversation data
 */
export async function getNativeSessionContent(baseDir: string, ccwId: string) {
  const store = await getSqliteStore(baseDir);
  return await store.getNativeSessionContent(ccwId);
}

/**
 * Get formatted native conversation text
 */
export async function getFormattedNativeConversation(baseDir: string, ccwId: string, options?: {
  includeThoughts?: boolean;
  includeToolCalls?: boolean;
  includeTokens?: boolean;
  maxContentLength?: number;
}) {
  const store = await getSqliteStore(baseDir);
  return await store.getFormattedNativeConversation(ccwId, options);
}

/**
 * Get conversation pairs from native session
 */
export async function getNativeConversationPairs(baseDir: string, ccwId: string) {
  const store = await getSqliteStore(baseDir);
  return await store.getNativeConversationPairs(ccwId);
}

/**
 * Get enriched conversation (CCW + native session merged)
 */
export async function getEnrichedConversation(baseDir: string, ccwId: string) {
  const store = await getSqliteStore(baseDir);
  return await store.getEnrichedConversation(ccwId);
}

/**
 * Get history with native session info
 * Supports recursive querying of child projects
 */
export async function getHistoryWithNativeInfo(baseDir: string, options?: {
  limit?: number;
  offset?: number;
  tool?: string | null;
  status?: string | null;
  category?: ExecutionCategory | null;
  search?: string | null;
  recursive?: boolean;
}) {
  const { limit = 50, recursive = false, ...queryOptions } = options || {};

  // Non-recursive mode: query single project
  if (!recursive) {
    const store = await getSqliteStore(baseDir);
    return store.getHistoryWithNativeInfo({ limit, ...queryOptions });
  }

  // Recursive mode: aggregate data from parent and all child projects
  const { scanChildProjectsAsync } = await import('../config/storage-paths.js');
  const childProjects = await scanChildProjectsAsync(baseDir);

  // Use the same type as store.getHistoryWithNativeInfo returns
  type ExecutionWithNativeAndSource = HistoryIndexEntry & {
    hasNativeSession: boolean;
    nativeSessionId?: string;
    nativeSessionPath?: string;
  };

  const allExecutions: ExecutionWithNativeAndSource[] = [];
  let totalCount = 0;

  // Query parent project
  try {
    const parentStore = await getSqliteStore(baseDir);
    const parentResult = parentStore.getHistoryWithNativeInfo({ limit, ...queryOptions });
    totalCount += parentResult.total;

    for (const exec of parentResult.executions) {
      allExecutions.push({ ...exec, sourceDir: baseDir });
    }
  } catch (error) {
    if (process.env.DEBUG) {
      console.error(`[CLI History] Failed to query parent project ${baseDir}:`, error);
    }
  }

  // Query all child projects
  for (const child of childProjects) {
    try {
      const childStore = await getSqliteStore(child.projectPath);
      const childResult = childStore.getHistoryWithNativeInfo({ limit, ...queryOptions });
      totalCount += childResult.total;

      for (const exec of childResult.executions) {
        allExecutions.push({ ...exec, sourceDir: child.projectPath });
      }
    } catch (error) {
      if (process.env.DEBUG) {
        console.error(`[CLI History] Failed to query child project ${child.projectPath}:`, error);
      }
    }
  }

  // Sort by updated_at descending and apply limit
  allExecutions.sort((a, b) => {
    const timeA = a.updated_at ? new Date(a.updated_at).getTime() : new Date(a.timestamp).getTime();
    const timeB = b.updated_at ? new Date(b.updated_at).getTime() : new Date(b.timestamp).getTime();
    return timeB - timeA;
  });
  const limitedExecutions = allExecutions.slice(0, limit);

  return {
    total: totalCount,
    count: limitedExecutions.length,
    executions: limitedExecutions
  };
}
