/**
 * `tbd uninstall` - Remove tbd from a repository.
 *
 * Removes the .tbd directory, worktree, and optionally the sync branch.
 */

import { Command } from 'commander';
import { rm, access, readdir, stat } from 'node:fs/promises';
import { execSync } from 'node:child_process';
import { join, relative } from 'node:path';

import { BaseCommand } from '../lib/base-command.js';
import { NotInitializedError, CLIError } from '../lib/errors.js';
import { findTbdRoot, readConfig } from '../../file/config.js';
import { SYNC_BRANCH, resolveSharedTbdPaths } from '../../lib/paths.js';

interface UninstallOptions {
  confirm?: boolean;
  keepBranch?: boolean;
  removeRemote?: boolean;
}

class UninstallHandler extends BaseCommand {
  async run(options: UninstallOptions): Promise<void> {
    const colors = this.output.getColors();

    // Resolve tbd root (walks up from cwd)
    const tbdRoot = await findTbdRoot(process.cwd());
    if (!tbdRoot) {
      throw new NotInitializedError('No .tbd directory found. Nothing to uninstall.');
    }

    // Read config to get branch info
    let config;
    try {
      config = await readConfig(tbdRoot);
    } catch {
      config = null;
    }

    const syncBranch = config?.sync.branch ?? SYNC_BRANCH;
    const remote = config?.sync.remote ?? 'origin';
    const tbdDir = join(tbdRoot, '.tbd');
    const sharedPaths = await resolveSharedTbdPaths(tbdRoot).catch((error: unknown) => {
      // Most often this is "not in a git repo" or git is unavailable; either way we
      // can still uninstall, but surface the real cause when --debug is set so the
      // operator can investigate later "Could not remove shared common-dir metadata".
      this.output.debug(
        `resolveSharedTbdPaths failed: ${error instanceof Error ? error.message : String(error)}`,
      );
      return null;
    });
    const worktreePath = sharedPaths?.sharedWorktreePath ?? join(tbdDir, 'data-sync-worktree');
    const legacyWorktreePath = join(tbdDir, 'data-sync-worktree');

    // Display paths relative to cwd for readability
    const displayPath = (p: string) => relative(process.cwd(), p) || p;

    // Check what exists
    const items: string[] = [];

    // Check worktree
    let worktreeExists = false;
    try {
      await access(worktreePath);
      worktreeExists = true;
      const worktreeStats = await this.getDirectoryStats(worktreePath);
      items.push(`  - Worktree: ${displayPath(worktreePath)} (${worktreeStats.files} files)`);
    } catch {
      // Worktree doesn't exist
    }

    let legacyWorktreeExists = false;
    if (legacyWorktreePath !== worktreePath) {
      try {
        await access(legacyWorktreePath);
        legacyWorktreeExists = true;
        const legacyStats = await this.getDirectoryStats(legacyWorktreePath);
        items.push(
          `  - Legacy worktree: ${displayPath(legacyWorktreePath)} (${legacyStats.files} files)`,
        );
      } catch {
        // Legacy worktree doesn't exist
      }
    }

    // Check local sync branch
    let localBranchExists = false;
    try {
      execSync(`git rev-parse --verify ${syncBranch}`, {
        encoding: 'utf-8',
        stdio: ['ignore', 'pipe', 'ignore'],
      });
      localBranchExists = true;
      if (!options.keepBranch) {
        items.push(`  - Local branch: ${syncBranch}`);
      }
    } catch {
      // Branch doesn't exist
    }

    // Check remote sync branch
    let remoteBranchExists = false;
    if (options.removeRemote) {
      try {
        execSync(`git rev-parse --verify ${remote}/${syncBranch}`, {
          encoding: 'utf-8',
          stdio: ['ignore', 'pipe', 'ignore'],
        });
        remoteBranchExists = true;
        items.push(`  - Remote branch: ${remote}/${syncBranch}`);
      } catch {
        // Remote branch doesn't exist
      }
    }

    // Count .tbd contents
    const tbdStats = await this.getDirectoryStats(tbdDir);
    items.push(`  - Directory: ${displayPath(tbdDir)}/ (${tbdStats.files} files)`);

    // Show what will be removed
    console.log(colors.bold('The following will be removed:'));
    console.log('');
    for (const item of items) {
      console.log(colors.warn(item));
    }
    console.log('');

    if (!options.confirm) {
      console.log(`This action is ${colors.bold('irreversible')}.`);
      console.log('');
      console.log(`To confirm, run: ${colors.dim('tbd uninstall --confirm')}`);
      if (!options.keepBranch && localBranchExists) {
        console.log(
          `To keep the sync branch: ${colors.dim('tbd uninstall --confirm --keep-branch')}`,
        );
      }
      if (!options.removeRemote) {
        console.log(
          `To also remove from remote: ${colors.dim('tbd uninstall --confirm --remove-remote')}`,
        );
      }
      return;
    }

    // Check dry-run
    if (this.checkDryRun('Would remove tbd from repository', { items })) {
      return;
    }

    // Perform uninstall
    this.output.info('Uninstalling tbd...');

    // 1. Remove worktree first (git worktree remove)
    if (worktreeExists) {
      try {
        // First try to remove the worktree through git
        execSync(`git worktree remove --force "${worktreePath}"`, {
          encoding: 'utf-8',
          stdio: ['ignore', 'pipe', 'ignore'],
        });
        console.log(`  ${colors.success('✓')} Removed git worktree`);
      } catch {
        // If git worktree remove fails, force delete the directory
        try {
          await rm(worktreePath, { recursive: true, force: true });
          console.log(`  ${colors.success('✓')} Removed worktree directory`);
        } catch {
          console.log(`  ${colors.warn('⚠')} Could not remove worktree directory`);
        }
      }
    }

    if (legacyWorktreeExists) {
      try {
        execSync(`git worktree remove --force "${legacyWorktreePath}"`, {
          encoding: 'utf-8',
          stdio: ['ignore', 'pipe', 'ignore'],
        });
        console.log(`  ${colors.success('✓')} Removed legacy git worktree`);
      } catch {
        await rm(legacyWorktreePath, { recursive: true, force: true });
        console.log(`  ${colors.success('✓')} Removed legacy worktree directory`);
      }
    }

    // 2. Remove local sync branch
    if (localBranchExists && !options.keepBranch) {
      try {
        execSync(`git branch -D ${syncBranch}`, {
          encoding: 'utf-8',
          stdio: ['ignore', 'pipe', 'ignore'],
        });
        console.log(`  ${colors.success('✓')} Removed local branch: ${syncBranch}`);
      } catch {
        console.log(`  ${colors.warn('⚠')} Could not remove local branch: ${syncBranch}`);
      }
    }

    // 3. Remove remote sync branch
    if (remoteBranchExists && options.removeRemote) {
      try {
        execSync(`git push ${remote} --delete ${syncBranch}`, {
          encoding: 'utf-8',
          stdio: ['ignore', 'pipe', 'ignore'],
        });
        console.log(`  ${colors.success('✓')} Removed remote branch: ${remote}/${syncBranch}`);
      } catch {
        console.log(
          `  ${colors.warn('⚠')} Could not remove remote branch: ${remote}/${syncBranch}`,
        );
      }
    }

    // 4. Clean up orphaned worktree references
    try {
      execSync('git worktree prune', {
        encoding: 'utf-8',
        stdio: ['ignore', 'pipe', 'ignore'],
      });
    } catch {
      // Ignore errors
    }

    // 5. Remove .tbd directory
    try {
      await rm(tbdDir, { recursive: true, force: true });
      console.log(`  ${colors.success('✓')} Removed .tbd directory`);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      throw new CLIError(`Failed to remove .tbd directory: ${message}`);
    }

    if (sharedPaths) {
      try {
        await rm(sharedPaths.sharedTbdDir, { recursive: true, force: true });
        console.log(`  ${colors.success('✓')} Removed shared common-dir metadata`);
      } catch {
        console.log(`  ${colors.warn('⚠')} Could not remove shared common-dir metadata`);
      }
    }

    console.log('');
    this.output.success('tbd has been uninstalled from this repository.');

    if (options.keepBranch && localBranchExists) {
      console.log('');
      console.log(colors.dim(`Note: The ${syncBranch} branch was preserved. Delete it with:`));
      console.log(colors.dim(`  git branch -D ${syncBranch}`));
    }

    if (!options.removeRemote && remoteBranchExists) {
      console.log('');
      console.log(
        colors.dim(
          `Note: The remote ${remote}/${syncBranch} branch was preserved. Delete it with:`,
        ),
      );
      console.log(colors.dim(`  git push ${remote} --delete ${syncBranch}`));
    }
  }

  /**
   * Get stats about a directory (file count, size).
   */
  private async getDirectoryStats(dirPath: string): Promise<{ files: number; size: number }> {
    let files = 0;
    let size = 0;

    const walk = async (dir: string): Promise<void> => {
      try {
        const entries = await readdir(dir, { withFileTypes: true });
        for (const entry of entries) {
          const fullPath = join(dir, entry.name);
          if (entry.isDirectory()) {
            await walk(fullPath);
          } else {
            files++;
            try {
              const stats = await stat(fullPath);
              size += stats.size;
            } catch {
              // Ignore stat errors
            }
          }
        }
      } catch {
        // Ignore errors
      }
    };

    await walk(dirPath);
    return { files, size };
  }
}

export const uninstallCommand = new Command('uninstall')
  .description('Remove tbd from this repository')
  .option('--confirm', 'Confirm removal (required to proceed)')
  .option('--keep-branch', 'Keep the local sync branch')
  .option('--remove-remote', 'Also remove the remote sync branch')
  .action(async (options, command) => {
    const handler = new UninstallHandler(command);
    await handler.run(options);
  });
