import { mkdirSync, cpSync, rmSync, existsSync, readFileSync, readdirSync } from 'fs';
import { resolve, basename } from 'path';
import type { Skill } from './types.js';
import { loadSkill } from './loader.js';
import { scanSkillDirectory } from './scanner.js';
import { auditLogger } from '../audit/index.js';

/**
 * SkillInstaller manages installing and removing skills in the managed
 * skills directory (e.g., ~/.flynn/workspace/skills/).
 *
 * Install copies a skill directory into managedDir, uninstall removes it.
 */
export class SkillInstaller {
  private managedDir: string;

  constructor(managedDir: string) {
    this.managedDir = managedDir;
    mkdirSync(this.managedDir, { recursive: true });
  }

  /**
   * Install a skill from a source directory into the managed area.
   *
   * - sourceDir must exist and contain a SKILL.md file.
   * - Skill name is read from manifest.json if present, otherwise falls back
   *   to the directory basename.
   * - If the skill is already installed, it is replaced (upgrade).
   *
   * Returns the loaded Skill, or null if loading failed after copy.
   */
  install(sourceDir: string): Skill | null {
    // Validate source directory exists and contains SKILL.md
    if (!existsSync(sourceDir)) {
      throw new Error(`Source directory does not exist: ${sourceDir}`);
    }
    const skillMdPath = resolve(sourceDir, 'SKILL.md');
    if (!existsSync(skillMdPath)) {
      throw new Error(`Source directory does not contain SKILL.md: ${sourceDir}`);
    }

    const scan = scanSkillDirectory(sourceDir);
    auditLogger?.skillsScan({
      skill_name: basename(sourceDir),
      tier: 'unknown',
      phase: 'install',
      ok: scan.ok,
      error_count: scan.issues.filter(i => i.severity === 'error').length,
      warn_count: scan.issues.filter(i => i.severity === 'warn').length,
      issue_codes: Array.from(new Set(scan.issues.map(i => i.code))),
    });
    if (!scan.ok) {
      const codes = Array.from(new Set(scan.issues.map(i => i.code))).join(', ');
      throw new Error(`Skill scan failed: ${codes || 'unknown_issue'}`);
    }

    // Determine skill name from manifest.json, or fall back to directory basename
    let skillName = basename(sourceDir);
    const manifestPath = resolve(sourceDir, 'manifest.json');
    if (existsSync(manifestPath)) {
      try {
        const manifestContent = readFileSync(manifestPath, 'utf-8');
        const manifest = JSON.parse(manifestContent) as { name?: string };
        if (manifest.name) {
          skillName = manifest.name;
        }
      } catch {
        // Fall back to basename if manifest can't be parsed
      }
    }

    const destination = resolve(this.managedDir, skillName);

    // Remove existing installation if present (upgrade path)
    if (existsSync(destination)) {
      rmSync(destination, { recursive: true, force: true });
    }

    // Copy source to managed directory
    cpSync(sourceDir, destination, { recursive: true });

    // Load and return the installed skill
    const skill = loadSkill(destination, 'managed');
    console.log(`Skill '${skillName}' installed to ${destination}`);
    return skill;
  }

  /**
   * Uninstall a managed skill by name.
   *
   * Returns true if the skill was found and removed, false otherwise.
   */
  uninstall(name: string): boolean {
    const dir = resolve(this.managedDir, name);
    if (!existsSync(dir)) {
      return false;
    }
    rmSync(dir, { recursive: true, force: true });
    console.log(`Skill '${name}' uninstalled`);
    return true;
  }

  /**
   * List basenames of all installed managed skills.
   *
   * A valid installed skill is a subdirectory containing a SKILL.md file.
   */
  listInstalled(): string[] {
    if (!existsSync(this.managedDir)) {
      return [];
    }

    const entries = readdirSync(this.managedDir, { withFileTypes: true });
    return entries
      .filter(entry => {
        if (!entry.isDirectory()) {return false;}
        const skillMd = resolve(this.managedDir, entry.name, 'SKILL.md');
        return existsSync(skillMd);
      })
      .map(entry => entry.name);
  }

  /** Check whether a skill with the given name is installed. */
  isInstalled(name: string): boolean {
    return existsSync(resolve(this.managedDir, name, 'SKILL.md'));
  }
}
