import { exec } from 'child_process';
import { describe, it, expect, test } from 'vitest';

function runCommand(command: string): Promise<string> {
  return new Promise((resolve, reject) => {
    exec(command, (error, stdout, stderr) => {
      if (error) {
        reject(stderr);
      } else {
        resolve(stdout);
      }
    });
  });
}

const cliTools = ['t', 'citty', 'cac', 'commander'];

describe.each(cliTools)('cli completion tests for %s', (cliTool) => {
  const commandPrefix = `pnpm tsx examples/demo.${cliTool}.ts complete --`;

  it('should complete cli options', async () => {
    const output = await runCommand(`${commandPrefix}`);
    expect(output).toMatchSnapshot();
  });

  describe('cli option completion tests', () => {
    const optionTests = [
      { partial: '--p', expected: '--port' },
      { partial: '-p', expected: '-p' }, // Test short flag completion
      { partial: '-H', expected: '-H' }, // Test another short flag completion
    ];

    test.each(optionTests)(
      "should complete option for partial input '%s'",
      async ({ partial }) => {
        const command = `${commandPrefix} dev ${partial}`;
        const output = await runCommand(command);
        expect(output).toMatchSnapshot();
      }
    );
  });

  describe('cli option exclusion tests', () => {
    const alreadySpecifiedTests = [
      { specified: '--config', shouldNotContain: '--config' },
    ];

    test.each(alreadySpecifiedTests)(
      "should not suggest already specified option '%s'",
      async ({ specified, shouldNotContain }) => {
        const command = `${commandPrefix} ${specified} --`;
        const output = await runCommand(command);
        expect(output).toMatchSnapshot();
      }
    );
  });

  describe('cli option value handling', () => {
    it('should resolve port value correctly', async () => {
      const command = `${commandPrefix} dev --port=3`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should not show duplicate options', async () => {
      const command = `${commandPrefix} --config vite.config.js --`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should resolve config option values correctly', async () => {
      const command = `${commandPrefix} --config vite.config`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should handle unknown options with no completions', async () => {
      const command = `${commandPrefix} --unknownoption`;
      const output = await runCommand(command);
      expect(output.trim()).toMatchSnapshot();
    });
  });

  describe('boolean option handling', () => {
    it('should complete subcommands and arguments after boolean options', async () => {
      const command = `${commandPrefix} dev --verbose ""`;
      const output = await runCommand(command);
      // After a boolean option, should show subcommands/arguments (not flag values)
      expect(output).toContain('build');
      expect(output).toContain('start');
      expect(output).not.toContain('verbose');
    });

    it('should complete subcommands and arguments after short boolean options', async () => {
      const command = `${commandPrefix} dev -v ""`;
      const output = await runCommand(command);
      // After a short boolean option, should show subcommands/arguments (not flag values)
      expect(output).toContain('build');
      expect(output).toContain('start');
      expect(output).not.toContain('verbose');
    });

    it('should not interfere with command completion after boolean options', async () => {
      const command = `${commandPrefix} dev --verbose s`;
      const output = await runCommand(command);
      // Should complete subcommands that start with 's' even after a boolean option
      expect(output).toContain('start');
    });

    it('should not interfere with option completion after boolean options', async () => {
      const command = `${commandPrefix} dev --verbose --h`;
      const output = await runCommand(command);
      // Should complete options that start with '--h' even after a boolean option
      expect(output).toContain('--host');
    });
  });

  describe('option API overload tests', () => {
    it('should handle basic option (name + description only) as boolean flag', async () => {
      // This tests the case: option('quiet', 'Suppress output')
      const command = `${commandPrefix} dev --quiet ""`;
      const output = await runCommand(command);
      // Should be treated as boolean flag — shows subcommands, not flag values
      expect(output).toContain('build');
      expect(output).not.toContain('quiet');
    });

    it('should handle option with alias only as boolean flag', async () => {
      // This tests the case: option('verbose', 'Enable verbose', 'v')
      const command = `${commandPrefix} dev --verbose ""`;
      const output = await runCommand(command);
      // Should be treated as boolean flag — shows subcommands, not flag values
      expect(output).toContain('build');
      expect(output).not.toContain('verbose');
    });

    it('should handle option with alias only (short flag) as boolean flag', async () => {
      // This tests the short flag version: -v instead of --verbose
      const command = `${commandPrefix} dev -v ""`;
      const output = await runCommand(command);
      // Should be treated as boolean flag — shows subcommands, not flag values
      expect(output).toContain('build');
      expect(output).not.toContain('verbose');
    });

    it('should handle option with handler only as value option', async () => {
      // This tests the case: option('port', 'Port number', handlerFunction)
      const command = `${commandPrefix} dev --port ""`;
      const output = await runCommand(command);
      // Should provide value completions because it has a handler
      expect(output).toContain('3000');
      expect(output).toContain('8080');
    });

    it('should handle option with both handler and alias as value option', async () => {
      // This tests the case: option('config', 'Config file', handlerFunction, 'c')
      const command = `${commandPrefix} --config ""`;
      const output = await runCommand(command);
      // Should provide value completions because it has a handler
      expect(output).toContain('vite.config.ts');
      expect(output).toContain('vite.config.js');
    });

    it('should handle option with both handler and alias (short flag) as value option', async () => {
      // This tests the short flag version with handler: -c instead of --config
      const command = `${commandPrefix} -c ""`;
      const output = await runCommand(command);
      // Should provide value completions because it has a handler
      expect(output).toContain('vite.config.ts');
      expect(output).toContain('vite.config.js');
    });

    it('should correctly detect boolean vs value options in mixed scenarios', async () => {
      // Test that boolean options don't interfere with value options
      const command = `${commandPrefix} dev --verbose --port ""`;
      const output = await runCommand(command);
      // Should complete port values, not be confused by preceding boolean flag
      expect(output).toContain('3000');
      expect(output).toContain('8080');
    });

    it('should correctly handle aliases for different option types', async () => {
      // Mix of boolean flag with alias (-v) and value option with alias (-p)
      const command = `${commandPrefix} dev -v -p ""`;
      const output = await runCommand(command);
      // Should complete port values via short flag
      expect(output).toContain('3000');
      expect(output).toContain('8080');
    });
  });

  describe('--config option tests', () => {
    it('should complete --config option values', async () => {
      const command = `${commandPrefix} --config ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete --config option with partial input', async () => {
      const command = `${commandPrefix} --config vite.config`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete --config option with equals sign', async () => {
      const command = `${commandPrefix} --config=vite.config`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete short flag -c option values', async () => {
      const command = `${commandPrefix} -c ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete short flag -c option with partial input', async () => {
      const command = `${commandPrefix} -c vite.config`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should not suggest --config after it has been used', async () => {
      const command = `${commandPrefix} --config vite.config.ts --`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });
  });

  describe('root command argument tests', () => {
    it('should complete root command project argument', async () => {
      const command = `${commandPrefix} ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete root command project argument with partial input', async () => {
      const command = `${commandPrefix} my-`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete root command project argument after options', async () => {
      const command = `${commandPrefix} --config vite.config.ts ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete root command project argument with options and partial input', async () => {
      const command = `${commandPrefix} --mode development my-`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });
  });

  describe('root command option tests', () => {
    it('should complete root command --mode option values', async () => {
      const command = `${commandPrefix} --mode ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete root command --mode option with partial input', async () => {
      const command = `${commandPrefix} --mode dev`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete root command --logLevel option values', async () => {
      const command = `${commandPrefix} --logLevel ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete root command --logLevel option with partial input', async () => {
      const command = `${commandPrefix} --logLevel i`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete root command short flag -m option values', async () => {
      const command = `${commandPrefix} -m ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete root command short flag -l option values', async () => {
      const command = `${commandPrefix} -l ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete root command options after project argument', async () => {
      const command = `${commandPrefix} my-app --`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete root command options with partial input after project argument', async () => {
      const command = `${commandPrefix} my-app --m`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });
  });

  describe('edge case completions for end with space', () => {
    it('should suggest port values if user ends with space after `--port`', async () => {
      const command = `${commandPrefix} dev --port ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it("should keep suggesting the --port option if user typed partial but didn't end with space", async () => {
      const command = `${commandPrefix} dev --po`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it("should suggest port values if user typed `--port=` and hasn't typed a space or value yet", async () => {
      const command = `${commandPrefix} dev --port=`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });
  });

  describe('short flag handling', () => {
    it('should handle short flag value completion', async () => {
      const command = `${commandPrefix} dev -p `;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should handle short flag with equals sign', async () => {
      const command = `${commandPrefix} dev -p=3`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should handle global short flags', async () => {
      const command = `${commandPrefix} -c `;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should not show duplicate options when short flag is used', async () => {
      const command = `${commandPrefix} -c vite.config.js --`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });
  });

  describe('positional argument completions', () => {
    it('should complete multiple positional arguments when ending with space', async () => {
      const command = `${commandPrefix} lint ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete multiple positional arguments when ending with part of the value', async () => {
      const command = `${commandPrefix} lint ind`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete single positional argument when ending with space', async () => {
      const command = `${commandPrefix} lint main.ts ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });
  });

  describe('copy command argument handlers', () => {
    it('should complete source argument with directory suggestions', async () => {
      const command = `${commandPrefix} copy ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should complete destination argument with build suggestions', async () => {
      const command = `${commandPrefix} copy src/ ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should filter source suggestions when typing partial input', async () => {
      const command = `${commandPrefix} copy s`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should filter destination suggestions when typing partial input', async () => {
      const command = `${commandPrefix} copy src/ b`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });
  });

  describe('lint command argument handlers', () => {
    it('should complete files argument with file suggestions', async () => {
      const command = `${commandPrefix} lint ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should filter file suggestions when typing partial input', async () => {
      const command = `${commandPrefix} lint m`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should continue completing variadic files argument after first file', async () => {
      const command = `${commandPrefix} lint main.ts ""`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });

    it('should continue completing variadic suggestions after first file', async () => {
      const command = `${commandPrefix} lint main.ts i`;
      const output = await runCommand(command);
      expect(output).toMatchSnapshot();
    });
  });
});

// Add specific tests for Commander
describe('commander specific tests', () => {
  it('should complete commands', async () => {
    const command = `pnpm tsx examples/demo.commander.ts complete -- `;
    const output = await runCommand(command);
    expect(output).toContain('serve');
    expect(output).toContain('build');
    expect(output).toContain('deploy');
  });

  it('should handle subcommands', async () => {
    // Check subcommands of root command.
    const command1 = `pnpm tsx examples/demo.commander.ts complete -- deploy`;
    const output1 = await runCommand(command1);
    expect(output1).toContain('deploy');
    expect(output1).toContain('Deploy the application');

    // Check subcommands of subcommand.
    const command2 = `pnpm tsx examples/demo.commander.ts complete -- deploy ""`;
    const output2 = await runCommand(command2);
    expect(output2).toContain('staging');
    expect(output2).toContain('production');
  });

  it('should intercept completion when using parseAsync', async () => {
    const command = `pnpm tsx examples/demo.commander-async.ts complete -- `;
    const output = await runCommand(command);
    expect(output).toContain('greet');
    expect(output).toContain('Say hello');
    // directive line must be present, proving t.parse was invoked
    expect(output).toMatch(/:\d+\s*$/);
  });
});

describe('shell completion script generation', () => {
  const shells = ['zsh', 'bash', 'fish', 'powershell'];
  const cliTool = 'commander'; // Use commander for shell script generation tests

  test.each(shells)('should generate %s completion script', async (shell) => {
    const command = `pnpm tsx examples/demo.${cliTool}.ts complete ${shell}`;
    const output = await runCommand(command);
    expect(output).toContain(`# ${shell} completion for`);
    expect(output.length).toBeGreaterThan(100); // Ensure we got a substantial script
  });
});
