import { spawn } from 'node:child_process';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import type { SecretStore } from './types.js';

const execFileAsync = promisify(execFile);
const SERVICE = 'mcpctl';

export type RunCommand = (cmd: string, args: string[], stdin?: string) => Promise<{ stdout: string; code: number }>;

function defaultRunCommand(cmd: string, args: string[], stdin?: string): Promise<{ stdout: string; code: number }> {
  return new Promise((resolve, reject) => {
    const child = spawn(cmd, args, {
      stdio: ['pipe', 'pipe', 'pipe'],
      timeout: 5000,
    });

    const stdoutChunks: Buffer[] = [];
    child.stdout.on('data', (chunk: Buffer) => stdoutChunks.push(chunk));

    child.on('error', reject);
    child.on('close', (code) => {
      const stdout = Buffer.concat(stdoutChunks).toString('utf-8');
      resolve({ stdout, code: code ?? 1 });
    });

    if (stdin !== undefined) {
      child.stdin.write(stdin);
      child.stdin.end();
    } else {
      child.stdin.end();
    }
  });
}

export interface GnomeKeyringDeps {
  run?: RunCommand;
}

export class GnomeKeyringStore implements SecretStore {
  private readonly run: RunCommand;

  constructor(deps?: GnomeKeyringDeps) {
    this.run = deps?.run ?? defaultRunCommand;
  }

  backend(): string {
    return 'gnome-keyring';
  }

  async get(key: string): Promise<string | null> {
    try {
      const { stdout, code } = await this.run(
        'secret-tool', ['lookup', 'service', SERVICE, 'key', key],
      );
      if (code !== 0 || !stdout) return null;
      return stdout;
    } catch {
      return null;
    }
  }

  async set(key: string, value: string): Promise<void> {
    const { code } = await this.run(
      'secret-tool',
      ['store', '--label', `mcpctl: ${key}`, 'service', SERVICE, 'key', key],
      value,
    );
    if (code !== 0) {
      throw new Error(`secret-tool store exited with code ${code}`);
    }
  }

  async delete(key: string): Promise<boolean> {
    try {
      const { code } = await this.run(
        'secret-tool', ['clear', 'service', SERVICE, 'key', key],
      );
      return code === 0;
    } catch {
      return false;
    }
  }

  static async isAvailable(deps?: { run?: RunCommand }): Promise<boolean> {
    try {
      if (deps?.run) {
        const { code } = await deps.run('secret-tool', ['--version']);
        return code === 0;
      }
      await execFileAsync('secret-tool', ['--version'], { timeout: 3000 });
      return true;
    } catch {
      return false;
    }
  }
}
