import { writeFile, readFile, mkdir } from 'fs/promises';
import { join } from 'path';
import { WorktreeManager, type CreateWorktreeOptions } from './worktree-manager.js';
import { AgentRunner } from '../agent/runner.js';
import { createDefaultRegistry } from '../agent/tools/registry.js';
import { loadConfig } from '../config.js';
import { createLogger, type Logger } from '../utils/logger.js';
import type { SwarmTask, SwarmState, MergeResult } from './types.js';

export interface TaskDefinition {
  title: string;
  description: string;
  model?: string;
}

export interface SwarmRunnerOptions {
  repoRoot: string;
  maxWorkers?: number;
  model?: string;
  logger?: Logger;
}

export interface TaskRunResult {
  taskId: string;
  success: boolean;
  response?: string;
  error?: string;
  filesModified: string[];
}

/**
 * Runs multiple tasks in parallel using git worktrees
 *
 * Each task gets its own isolated worktree with a dedicated branch.
 * Tasks can be executed concurrently and merged back when complete.
 */
export class SwarmRunner {
  private worktreeManager: WorktreeManager;
  private repoRoot: string;
  private maxWorkers: number;
  private model: string;
  private logger: Logger;
  private stateFile: string;
  private state: SwarmState | null = null;

  constructor(options: SwarmRunnerOptions) {
    this.repoRoot = options.repoRoot;
    this.maxWorkers = options.maxWorkers ?? 3;
    this.model = options.model ?? 'claude-sonnet-4-20250514';
    this.logger = options.logger ?? createLogger({ name: 'swarm-runner' });
    this.worktreeManager = new WorktreeManager(options.repoRoot);
    this.stateFile = join(options.repoRoot, '.emdash-worktrees', '.swarm-state.json');
  }

  /**
   * Initialize the swarm runner
   */
  async init(): Promise<void> {
    await this.worktreeManager.init();
    await this.loadState();
  }

  /**
   * Run multiple tasks in parallel
   */
  async run(tasks: TaskDefinition[]): Promise<SwarmState> {
    await this.init();

    const { simpleGit } = await import('simple-git');
    const git = simpleGit(this.repoRoot);
    const baseBranch = (await git.revparse(['--abbrev-ref', 'HEAD'])).trim();

    // Initialize state
    this.state = {
      tasks: [],
      startedAt: new Date().toISOString(),
      baseBranch,
    };

    // Create worktrees for each task
    this.logger.info({ taskCount: tasks.length }, 'Creating worktrees for tasks');

    for (const task of tasks) {
      const id = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
      const swarmTask = await this.worktreeManager.createWorktree({
        id,
        title: task.title,
        description: task.description,
      });
      this.state.tasks.push(swarmTask);
    }

    await this.saveState();

    // Run tasks with concurrency limit
    this.logger.info({ maxWorkers: this.maxWorkers }, 'Running tasks');

    const results: TaskRunResult[] = [];
    const taskQueue = [...this.state.tasks];

    while (taskQueue.length > 0) {
      // Take up to maxWorkers tasks
      const batch = taskQueue.splice(0, this.maxWorkers);

      // Run batch in parallel
      const batchResults = await Promise.all(
        batch.map((task) => this.runTask(task, tasks.find((t) => t.title === task.title)!))
      );

      results.push(...batchResults);
    }

    // Update state with results
    this.state.completedAt = new Date().toISOString();
    await this.saveState();

    this.logger.info(
      {
        completed: results.filter((r) => r.success).length,
        failed: results.filter((r) => !r.success).length,
      },
      'Swarm run complete'
    );

    return this.state;
  }

  /**
   * Run a single task in its worktree
   */
  private async runTask(task: SwarmTask, definition: TaskDefinition): Promise<TaskRunResult> {
    this.logger.info({ taskId: task.id, title: task.title }, 'Starting task');

    // Update status
    await this.worktreeManager.updateTaskStatus(task.id, 'running');
    await this.saveState();

    try {
      const config = loadConfig();
      const logger = createLogger({ name: `task-${task.id}` });

      // Create agent runner for this worktree
      const runner = new AgentRunner({
        config,
        logger,
        repoRoot: task.worktreePath,
        model: definition.model ?? this.model,
        mode: 'code',
        tools: createDefaultRegistry(),
      });

      // Run the agent with the task description
      const prompt = `Complete the following task:

## Task: ${task.title}

${definition.description}

Work in the current directory. Make all necessary changes and commit them when done.`;

      const result = await runner.run(prompt);

      // Commit any uncommitted changes
      const { simpleGit } = await import('simple-git');
      const git = simpleGit(task.worktreePath);
      const status = await git.status();

      if (status.files.length > 0) {
        await git.add('.');
        await git.commit(`Complete: ${task.title}`);
      }

      // Get list of modified files
      const diffResult = await git.diff(['--name-only', `${this.state!.baseBranch}...HEAD`]);
      const filesModified = diffResult.trim().split('\n').filter(Boolean);

      // Update task status
      await this.worktreeManager.updateTaskStatus(task.id, 'completed', {
        filesModified,
        summary: result.response.slice(0, 500),
      });

      this.logger.info({ taskId: task.id, filesModified: filesModified.length }, 'Task completed');

      return {
        taskId: task.id,
        success: true,
        response: result.response,
        filesModified,
      };
    } catch (err) {
      const error = err instanceof Error ? err.message : String(err);
      this.logger.error({ taskId: task.id, error }, 'Task failed');

      await this.worktreeManager.updateTaskStatus(task.id, 'failed', { error });

      return {
        taskId: task.id,
        success: false,
        error,
        filesModified: [],
      };
    }
  }

  /**
   * Merge all completed tasks back to the base branch
   */
  async mergeCompleted(): Promise<MergeResult[]> {
    if (!this.state) {
      await this.loadState();
    }

    if (!this.state) {
      throw new Error('No swarm state found');
    }

    const results: MergeResult[] = [];
    const completedTasks = this.state.tasks.filter((t) => t.status === 'completed');

    for (const task of completedTasks) {
      this.logger.info({ taskId: task.id, branch: task.branch }, 'Merging task');
      const result = await this.worktreeManager.mergeWorktree(task.id);
      results.push(result);

      if (result.success) {
        this.logger.info({ taskId: task.id }, 'Task merged successfully');
      } else {
        this.logger.warn({ taskId: task.id, conflicts: result.conflicts }, 'Merge failed');
      }
    }

    await this.saveState();
    return results;
  }

  /**
   * Get the current state
   */
  getState(): SwarmState | null {
    return this.state;
  }

  /**
   * Get list of tasks
   */
  async listTasks(): Promise<SwarmTask[]> {
    return this.worktreeManager.listWorktrees();
  }

  /**
   * Clean up all worktrees
   */
  async cleanup(): Promise<void> {
    await this.worktreeManager.cleanup();
    this.state = null;
  }

  /**
   * Save state to disk
   */
  private async saveState(): Promise<void> {
    if (!this.state) return;

    // Reload tasks from worktree manager for latest status
    this.state.tasks = await this.worktreeManager.listWorktrees();

    const dir = join(this.repoRoot, '.emdash-worktrees');
    await mkdir(dir, { recursive: true });
    await writeFile(this.stateFile, JSON.stringify(this.state, null, 2), 'utf-8');
  }

  /**
   * Load state from disk
   */
  private async loadState(): Promise<void> {
    try {
      const content = await readFile(this.stateFile, 'utf-8');
      this.state = JSON.parse(content);

      // Refresh task statuses from worktree manager
      if (this.state) {
        this.state.tasks = await this.worktreeManager.listWorktrees();
      }
    } catch {
      this.state = null;
    }
  }
}
