import { access, readdir } from "node:fs/promises";
import { homedir } from "node:os";
import { basename, join, resolve } from "node:path";

import { getPilotExtensionPaths } from "../../pilot/paths.js";
import { SkillManager, SkillManagerError, SkillValidationError } from "./SkillManager.js";
import type { SkillImportResult, SkillScope, SkillValidationResult } from "./types.js";

export type SkillMigrationSourceKind = "claude-code" | "openclaw" | "hermes" | "custom";
export type SkillMigrationConflictMode = "skip" | "overwrite" | "rename";

export type SkillMigrationSource = {
  kind: SkillMigrationSourceKind;
  label: string;
  path: string;
};

export type SkillMigrationItemStatus =
  | "migrated"
  | "would_migrate"
  | "skipped"
  | "conflict"
  | "error";

export type SkillMigrationItem = {
  sourceKind: SkillMigrationSourceKind;
  sourceLabel: string;
  sourcePath: string;
  destinationPath: string | null;
  slug: string;
  status: SkillMigrationItemStatus;
  reason: string;
  validation?: SkillValidationResult;
};

export type SkillMigrationReport = {
  mode: "execute" | "dry-run";
  targetRoot: string;
  sources: SkillMigrationSource[];
  summary: Record<SkillMigrationItemStatus, number>;
  items: SkillMigrationItem[];
};

export type MigrateSkillsToPilotDeckOptions = {
  pilotHome: string;
  projectRoot?: string;
  include?: Array<Exclude<SkillMigrationSourceKind, "custom">>;
  customSources?: string[];
  execute?: boolean;
  conflictMode?: SkillMigrationConflictMode;
  scope?: SkillScope;
  projectKey?: string | null;
};

const DEFAULT_INCLUDE: Array<Exclude<SkillMigrationSourceKind, "custom">> = [
  "claude-code",
  "openclaw",
  "hermes",
];

export async function migrateSkillsToPilotDeck(
  options: MigrateSkillsToPilotDeckOptions,
): Promise<SkillMigrationReport> {
  const pilotHome = resolve(options.pilotHome);
  const scope = options.scope ?? "user";
  const projectKey = options.projectKey ?? options.projectRoot ?? null;
  const targetRoot =
    scope === "project" && projectKey
      ? getPilotExtensionPaths(resolve(projectKey), pilotHome).projectSkillsDir
      : getPilotExtensionPaths(pilotHome, pilotHome).globalSkillsDir;
  const execute = options.execute === true;
  const conflictMode = options.conflictMode ?? "skip";
  const manager = new SkillManager({ pilotHome });
  const sources = dedupeSources(buildSources(options));
  const items: SkillMigrationItem[] = [];

  for (const source of sources) {
    const skillDirs = await discoverSkillDirs(source.path);
    if (skillDirs === null) {
      items.push({
        sourceKind: source.kind,
        sourceLabel: source.label,
        sourcePath: source.path,
        destinationPath: null,
        slug: "",
        status: "skipped",
        reason: "Source directory does not exist.",
      });
      continue;
    }
    if (skillDirs.length === 0) {
      items.push({
        sourceKind: source.kind,
        sourceLabel: source.label,
        sourcePath: source.path,
        destinationPath: null,
        slug: "",
        status: "skipped",
        reason: "No immediate child skill directories with SKILL.md found.",
      });
      continue;
    }

    for (const sourcePath of skillDirs) {
      const sourceSlug = basename(sourcePath);
      if (!isValidSlug(sourceSlug)) {
        items.push({
          sourceKind: source.kind,
          sourceLabel: source.label,
          sourcePath,
          destinationPath: null,
          slug: sourceSlug,
          status: "error",
          reason: `Invalid slug "${sourceSlug}".`,
        });
        continue;
      }
      const slug = await resolveDestinationSlug(targetRoot, sourceSlug, conflictMode);
      if (slug === null) {
        items.push({
          sourceKind: source.kind,
          sourceLabel: source.label,
          sourcePath,
          destinationPath: join(targetRoot, sourceSlug),
          slug: sourceSlug,
          status: "conflict",
          reason: "Destination skill already exists.",
        });
        continue;
      }
      const destinationPath = join(targetRoot, slug);

      try {
        if (!execute) {
          const validation = await manager.validate({ sourcePath });
          if (!validation.ok) {
            items.push({
              sourceKind: source.kind,
              sourceLabel: source.label,
              sourcePath,
              destinationPath,
              slug,
              status: "error",
              reason: `validation_failed: ${validation.hardFails.map((issue) => issue.message).join("; ")}`,
              validation,
            });
            continue;
          }
          items.push({
            sourceKind: source.kind,
            sourceLabel: source.label,
            sourcePath,
            destinationPath,
            slug,
            status: "would_migrate",
            reason: slug === sourceSlug ? "Would copy skill." : `Would copy skill as ${slug}.`,
            validation,
          });
          continue;
        }

        const result = await manager.import({
          sourcePath,
          slug,
          scope,
          projectKey,
          mode: "copy",
          force: conflictMode === "overwrite",
        });
        items.push(toMigratedItem(source, result));
      } catch (error) {
        items.push(toErrorItem(source, sourcePath, destinationPath, slug, error));
      }
    }
  }

  return {
    mode: execute ? "execute" : "dry-run",
    targetRoot,
    sources,
    summary: summarize(items),
    items,
  };
}

function buildSources(options: MigrateSkillsToPilotDeckOptions): SkillMigrationSource[] {
  const home = homedir();
  const projectRoot = options.projectRoot ? resolve(options.projectRoot) : resolve(process.cwd());
  const include = options.include ?? DEFAULT_INCLUDE;
  const sources: SkillMigrationSource[] = [];

  for (const kind of include) {
    if (kind === "claude-code") {
      sources.push(
        { kind, label: "Claude Code user skills", path: join(home, ".claude", "skills") },
        { kind, label: "Claude Code project skills", path: join(projectRoot, ".claude", "skills") },
      );
    } else if (kind === "openclaw") {
      const openclawHome = join(home, ".openclaw");
      sources.push(
        { kind, label: "OpenClaw workspace skills", path: join(openclawHome, "workspace", "skills") },
        { kind, label: "OpenClaw workspace-main skills", path: join(openclawHome, "workspace-main", "skills") },
        { kind, label: "OpenClaw workspace-assistant skills", path: join(openclawHome, "workspace-assistant", "skills") },
        { kind, label: "OpenClaw managed skills", path: join(openclawHome, "skills") },
        { kind, label: "OpenClaw shared skills", path: join(home, ".agents", "skills") },
        { kind, label: "OpenClaw project shared skills", path: join(openclawHome, "workspace", ".agents", "skills") },
        { kind, label: "OpenClaw default shared skills", path: join(openclawHome, "workspace.default", ".agents", "skills") },
      );
    } else if (kind === "hermes") {
      const hermesHome = join(home, ".hermes");
      sources.push(
        { kind, label: "Hermes user skills", path: join(hermesHome, "skills") },
        { kind, label: "Hermes Claude-style skills", path: join(hermesHome, ".claude", "skills") },
        { kind, label: "Hermes agent skills", path: join(hermesHome, ".agents", "skills") },
      );
    }
  }

  for (const sourcePath of options.customSources ?? []) {
    sources.push({
      kind: "custom",
      label: "Custom skills",
      path: resolve(expandHome(sourcePath)),
    });
  }

  return sources.map((source) => ({ ...source, path: resolve(source.path) }));
}

function dedupeSources(sources: SkillMigrationSource[]): SkillMigrationSource[] {
  const seen = new Set<string>();
  const out: SkillMigrationSource[] = [];
  for (const source of sources) {
    const key = source.path;
    if (seen.has(key)) continue;
    seen.add(key);
    out.push(source);
  }
  return out;
}

async function discoverSkillDirs(sourceRoot: string): Promise<string[] | null> {
  try {
    await access(join(sourceRoot, "SKILL.md"));
    return [sourceRoot];
  } catch {
    /* Source root is a parent directory, not a single skill. */
  }

  let entries: import("node:fs").Dirent[];
  try {
    entries = await readdir(sourceRoot, { withFileTypes: true });
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
    throw error;
  }

  const dirs: string[] = [];
  for (const entry of entries) {
    if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
    const skillDir = join(sourceRoot, entry.name);
    try {
      await access(join(skillDir, "SKILL.md"));
      dirs.push(skillDir);
    } catch {
      continue;
    }
  }
  return dirs.sort((a, b) => basename(a).localeCompare(basename(b)));
}

async function resolveDestinationSlug(
  targetRoot: string,
  sourceSlug: string,
  conflictMode: SkillMigrationConflictMode,
): Promise<string | null> {
  if (conflictMode === "overwrite") return sourceSlug;
  const target = join(targetRoot, sourceSlug);
  const exists = await pathExists(target);
  if (!exists) return sourceSlug;
  if (conflictMode === "skip") return null;

  let counter = 2;
  let candidate = `${sourceSlug}-imported`;
  while (await pathExists(join(targetRoot, candidate))) {
    candidate = `${sourceSlug}-imported-${counter}`;
    counter++;
  }
  return candidate;
}

function toMigratedItem(source: SkillMigrationSource, result: SkillImportResult): SkillMigrationItem {
  return {
    sourceKind: source.kind,
    sourceLabel: source.label,
    sourcePath: result.sourcePath,
    destinationPath: result.skillPath,
    slug: result.slug,
    status: "migrated",
    reason: "Copied skill.",
    validation: result.validation,
  };
}

function toErrorItem(
  source: SkillMigrationSource,
  sourcePath: string,
  destinationPath: string,
  slug: string,
  error: unknown,
): SkillMigrationItem {
  let reason = error instanceof Error ? error.message : String(error);
  if (error instanceof SkillManagerError) {
    reason = `${error.code}: ${error.message}`;
  }
  if (error instanceof SkillValidationError) {
    reason = `validation_failed: ${error.validation.hardFails.map((issue) => issue.message).join("; ")}`;
  }
  return {
    sourceKind: source.kind,
    sourceLabel: source.label,
    sourcePath,
    destinationPath,
    slug,
    status: "error",
    reason,
  };
}

function summarize(items: SkillMigrationItem[]): Record<SkillMigrationItemStatus, number> {
  const summary: Record<SkillMigrationItemStatus, number> = {
    migrated: 0,
    would_migrate: 0,
    skipped: 0,
    conflict: 0,
    error: 0,
  };
  for (const item of items) {
    summary[item.status] += 1;
  }
  return summary;
}

async function pathExists(path: string): Promise<boolean> {
  try {
    await access(path);
    return true;
  } catch {
    return false;
  }
}

function expandHome(path: string): string {
  if (path === "~") return homedir();
  if (path.startsWith("~/")) return join(homedir(), path.slice(2));
  return path;
}

function isValidSlug(slug: string): boolean {
  return /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,99}$/.test(slug) && !slug.includes("..");
}
