import { Command } from 'commander';
import { writeFileSync, readFileSync, unlinkSync, mkdtempSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { execSync } from 'node:child_process';
import yaml from 'js-yaml';
import type { ApiClient } from '../api-client.js';
import { resolveResource, resolveNameOrId, stripInternalFields } from './shared.js';
import { reorderKeys } from '../formatters/output.js';

export interface EditCommandDeps {
  client: ApiClient;
  log: (...args: unknown[]) => void;
  /** Override for testing — return editor binary name. */
  getEditor?: () => string;
  /** Override for testing — simulate opening the editor. */
  openEditor?: (filePath: string, editor: string) => void;
}

function getEditor(deps: EditCommandDeps): string {
  if (deps.getEditor) return deps.getEditor();
  return process.env.VISUAL ?? process.env.EDITOR ?? 'vi';
}

function openEditor(filePath: string, editor: string, deps: EditCommandDeps): void {
  if (deps.openEditor) {
    deps.openEditor(filePath, editor);
    return;
  }
  execSync(`${editor} "${filePath}"`, { stdio: 'inherit' });
}

export function createEditCommand(deps: EditCommandDeps): Command {
  const { client, log } = deps;

  return new Command('edit')
    .description('Edit a resource in your default editor (server, project)')
    .argument('<resource>', 'Resource type (server, project)')
    .argument('<name-or-id>', 'Resource name or ID')
    .option('--bump <kind>', 'Bump prompt semver after edit: major | minor | patch')
    .option('--semver <version>', 'Set prompt semver explicitly (X.Y.Z)')
    .option('--note <message>', 'Note attached to the resulting revision')
    .action(async (resourceArg: string, nameOrId: string, opts: { bump?: string; semver?: string; note?: string }) => {
      const resource = resolveResource(resourceArg);

      // Instances are immutable
      if (resource === 'instances') {
        log('Error: instances are immutable and cannot be edited.');
        log('To change an instance, update the server definition and let reconciliation handle it.');
        process.exitCode = 1;
        return;
      }

      const validResources = ['servers', 'secrets', 'projects', 'groups', 'rbac', 'prompts', 'promptrequests', 'personalities'];
      if (!validResources.includes(resource)) {
        log(`Error: unknown resource type '${resourceArg}'`);
        process.exitCode = 1;
        return;
      }

      // Validation for prompt-only revision flags
      if ((opts.bump !== undefined || opts.semver !== undefined || opts.note !== undefined) && resource !== 'prompts') {
        log('Error: --bump, --semver, and --note are only valid for prompts');
        process.exitCode = 1;
        return;
      }
      if (opts.bump !== undefined && opts.semver !== undefined) {
        log('Error: pass --bump or --semver, not both');
        process.exitCode = 1;
        return;
      }
      if (opts.bump !== undefined && !['major', 'minor', 'patch'].includes(opts.bump)) {
        log("Error: --bump must be 'major', 'minor', or 'patch'");
        process.exitCode = 1;
        return;
      }

      // Resolve name → ID
      const id = await resolveNameOrId(client, resource, nameOrId);

      // Fetch current state
      const current = await client.get<Record<string, unknown>>(`/api/v1/${resource}/${id}`);

      // Strip read-only fields for editor
      const editable = reorderKeys(stripInternalFields(current)) as Record<string, unknown>;

      // Serialize to YAML
      const singular = resource.replace(/s$/, '');
      const header = `# Editing ${singular}: ${nameOrId}\n# Save and close to apply changes. Clear the file to cancel.\n`;
      const originalYaml = yaml.dump(editable, { lineWidth: 120, noRefs: true });
      const content = header + originalYaml;

      // Write to temp file
      const tmpDir = mkdtempSync(join(tmpdir(), 'mcpctl-edit-'));
      const tmpFile = join(tmpDir, `${singular}-${nameOrId}.yaml`);
      writeFileSync(tmpFile, content, 'utf-8');

      try {
        // Open editor
        const editor = getEditor(deps);
        openEditor(tmpFile, editor, deps);

        // Read back
        const modified = readFileSync(tmpFile, 'utf-8');

        // Strip comments for comparison
        const modifiedClean = modified
          .split('\n')
          .filter((line) => !line.startsWith('#'))
          .join('\n')
          .trim();

        if (!modifiedClean) {
          log('Edit cancelled (empty file).');
          return;
        }

        if (modifiedClean === originalYaml.trim()) {
          log(`${singular} '${nameOrId}' unchanged.`);
          return;
        }

        // Parse and apply
        const updates = yaml.load(modifiedClean) as Record<string, unknown>;
        // Append semver-related flags for prompts (server-side bumps + records revision).
        if (resource === 'prompts') {
          if (opts.bump !== undefined) updates.bump = opts.bump;
          if (opts.semver !== undefined) updates.semver = opts.semver;
          if (opts.note !== undefined) updates.note = opts.note;
        }
        await client.put(`/api/v1/${resource}/${id}`, updates);
        log(`${singular} '${nameOrId}' updated.`);
      } finally {
        try {
          unlinkSync(tmpFile);
        } catch {
          // Ignore cleanup errors
        }
      }
    });
}
