import { simpleGit, type SimpleGit } from 'simple-git';
import { mkdir, rm, writeFile, readFile, access } from 'fs/promises';
import { join, resolve } from 'path';
import type { SwarmTask, TaskStatus, MergeResult } from './types.js';

/**
 * Options for creating a worktree
 */
export interface CreateWorktreeOptions {
  id: string;
  title: string;
  description?: string;
}

/**
 * Manages Git worktrees for parallel task execution
 *
 * Each task gets its own worktree with a dedicated branch,
 * allowing multiple agents to work on different tasks simultaneously.
 */
export class WorktreeManager {
  private git: SimpleGit;
  private repoRoot: string;
  private worktreesDir: string;

  constructor(repoRoot: string) {
    this.repoRoot = resolve(repoRoot);
    this.git = simpleGit(this.repoRoot);
    this.worktreesDir = join(this.repoRoot, '.emdash-worktrees');
  }

  /**
   * Initialize the worktrees directory
   */
  async init(): Promise<void> {
    await mkdir(this.worktreesDir, { recursive: true });
  }

  /**
   * Create a new worktree for a task
   */
  async createWorktree(options: CreateWorktreeOptions): Promise<SwarmTask> {
    const slug = this.slugify(options.title);
    const branch = `swarm/${options.id}-${slug}`;
    const worktreePath = join(this.worktreesDir, `${options.id}-${slug}`);

    // Get current branch as base
    const currentBranch = await this.git.revparse(['--abbrev-ref', 'HEAD']);
    const baseBranch = currentBranch.trim();

    // Create worktree with new branch
    await this.git.raw([
      'worktree',
      'add',
      '-b',
      branch,
      worktreePath,
      baseBranch,
    ]);

    const task: SwarmTask = {
      id: options.id,
      slug,
      title: options.title,
      description: options.description ?? '',
      branch,
      worktreePath,
      status: 'pending',
      filesModified: [],
    };

    // Save task metadata
    await this.saveTaskMeta(task);

    return task;
  }

  /**
   * Remove a worktree and optionally its branch
   */
  async removeWorktree(taskId: string, deleteBranch = false): Promise<void> {
    const task = await this.getTask(taskId);
    if (!task) return;

    // Remove worktree
    await this.git.raw(['worktree', 'remove', '--force', task.worktreePath]).catch(() => {
      // Worktree might already be removed
    });

    // Delete branch if requested and not merged
    if (deleteBranch && task.status !== 'merged') {
      await this.git.branch(['-D', task.branch]).catch(() => {
        // Branch might not exist
      });
    }

    // Remove worktree directory if it still exists
    await rm(task.worktreePath, { recursive: true, force: true }).catch(() => {});
  }

  /**
   * Merge a completed task's branch back to the base branch
   */
  async mergeWorktree(taskId: string): Promise<MergeResult> {
    const task = await this.getTask(taskId);

    if (!task) {
      return {
        success: false,
        taskId,
        branch: '',
        error: `Task ${taskId} not found`,
      };
    }

    if (task.status !== 'completed') {
      return {
        success: false,
        taskId,
        branch: task.branch,
        error: `Task ${taskId} is not completed (status: ${task.status})`,
      };
    }

    try {
      // Merge the branch with a commit message
      await this.git.merge([
        task.branch,
        '--no-ff',
        '-m',
        `Merge swarm task: ${task.title}`,
      ]);

      // Update task status
      await this.updateTaskStatus(taskId, 'merged');

      return {
        success: true,
        taskId,
        branch: task.branch,
      };
    } catch (err) {
      // Check for merge conflicts
      const status = await this.git.status();

      if (status.conflicted.length > 0) {
        // Abort the merge
        await this.git.merge(['--abort']);

        return {
          success: false,
          taskId,
          branch: task.branch,
          conflicts: status.conflicted,
        };
      }

      return {
        success: false,
        taskId,
        branch: task.branch,
        error: err instanceof Error ? err.message : String(err),
      };
    }
  }

  /**
   * List all worktrees and their tasks
   */
  async listWorktrees(): Promise<SwarmTask[]> {
    const tasks: SwarmTask[] = [];

    try {
      const worktreeList = await this.git.raw(['worktree', 'list', '--porcelain']);
      const lines = worktreeList.split('\n');

      for (let i = 0; i < lines.length; i++) {
        const line = lines[i];

        if (line.startsWith('worktree ') && line.includes('.emdash-worktrees')) {
          const path = line.replace('worktree ', '');
          const metaPath = join(path, '.emdash-task', 'task.json');

          try {
            await access(metaPath);
            const meta = JSON.parse(await readFile(metaPath, 'utf-8'));
            tasks.push(meta);
          } catch {
            // No metadata file, skip
          }
        }
      }
    } catch {
      // No worktrees or git error
    }

    return tasks;
  }

  /**
   * Get a specific task by ID
   */
  async getTask(taskId: string): Promise<SwarmTask | null> {
    const tasks = await this.listWorktrees();
    return tasks.find((t) => t.id === taskId) ?? null;
  }

  /**
   * Update task status
   */
  async updateTaskStatus(
    taskId: string,
    status: TaskStatus,
    updates?: Partial<SwarmTask>
  ): Promise<void> {
    const task = await this.getTask(taskId);
    if (!task) {
      throw new Error(`Task ${taskId} not found`);
    }

    task.status = status;

    if (updates) {
      Object.assign(task, updates);
    }

    // Set timestamps
    if (status === 'running' && !task.startedAt) {
      task.startedAt = new Date().toISOString();
    }
    if (status === 'completed' || status === 'failed') {
      task.completedAt = new Date().toISOString();
    }

    await this.saveTaskMeta(task);
  }

  /**
   * Get the git instance for a worktree
   */
  getWorktreeGit(task: SwarmTask): SimpleGit {
    return simpleGit(task.worktreePath);
  }

  /**
   * Commit changes in a worktree
   */
  async commitInWorktree(
    task: SwarmTask,
    message: string
  ): Promise<{ hash: string; filesChanged: string[] }> {
    const git = this.getWorktreeGit(task);

    // Stage all changes
    await git.add('.');

    // Get list of staged files
    const status = await git.status();
    const filesChanged = [
      ...status.staged,
      ...status.modified.filter((f) => !status.staged.includes(f)),
    ];

    // Commit
    const result = await git.commit(message);

    // Update task metadata
    task.filesModified = [...new Set([...task.filesModified, ...filesChanged])];
    await this.saveTaskMeta(task);

    return {
      hash: result.commit,
      filesChanged,
    };
  }

  /**
   * Save task metadata to the worktree
   */
  private async saveTaskMeta(task: SwarmTask): Promise<void> {
    const metaDir = join(task.worktreePath, '.emdash-task');
    await mkdir(metaDir, { recursive: true });
    await writeFile(
      join(metaDir, 'task.json'),
      JSON.stringify(task, null, 2),
      'utf-8'
    );
  }

  /**
   * Generate a URL-safe slug from text
   */
  private slugify(text: string): string {
    return text
      .toLowerCase()
      .replace(/[^a-z0-9]+/g, '-')
      .replace(/^-|-$/g, '')
      .slice(0, 30);
  }

  /**
   * Clean up all worktrees and branches
   */
  async cleanup(): Promise<void> {
    const tasks = await this.listWorktrees();

    for (const task of tasks) {
      await this.removeWorktree(task.id, true);
    }

    // Remove worktrees directory
    await rm(this.worktreesDir, { recursive: true, force: true }).catch(() => {});
  }
}

export type { SwarmTask };
