import { describe, expect, test } from 'bun:test';
import { checkCustomRules } from '../src/core/rules-custom.ts';
import type { CustomRule } from '../src/types.ts';

describe('custom rule matching', () => {
  test('basic command match', () => {
    const rules: CustomRule[] = [
      {
        name: 'block-git-add-all',
        command: 'git',
        subcommand: 'add',
        block_args: ['-A', '--all'],
        reason: 'Use specific files.',
      },
    ];
    const result = checkCustomRules(['git', 'add', '-A'], rules);
    expect(result).toBe('[block-git-add-all] Use specific files.');
  });

  test('match with long option form', () => {
    const rules: CustomRule[] = [
      {
        name: 'block-git-add-all',
        command: 'git',
        subcommand: 'add',
        block_args: ['-A', '--all'],
        reason: 'Use specific files.',
      },
    ];
    const result = checkCustomRules(['git', 'add', '--all'], rules);
    expect(result).toBe('[block-git-add-all] Use specific files.');
  });

  test('no match when command differs', () => {
    const rules: CustomRule[] = [
      {
        name: 'block-git-add-all',
        command: 'git',
        subcommand: 'add',
        block_args: ['-A'],
        reason: 'test',
      },
    ];
    const result = checkCustomRules(['npm', 'add', '-A'], rules);
    expect(result).toBeNull();
  });

  test('no match when subcommand differs', () => {
    const rules: CustomRule[] = [
      {
        name: 'block-git-add-all',
        command: 'git',
        subcommand: 'add',
        block_args: ['-A'],
        reason: 'test',
      },
    ];
    const result = checkCustomRules(['git', 'commit', '-A'], rules);
    expect(result).toBeNull();
  });

  test('no match when no blocked args present', () => {
    const rules: CustomRule[] = [
      {
        name: 'block-git-add-all',
        command: 'git',
        subcommand: 'add',
        block_args: ['-A', '--all'],
        reason: 'test',
      },
    ];
    const result = checkCustomRules(['git', 'add', 'file.txt'], rules);
    expect(result).toBeNull();
  });

  test('rule without subcommand matches any invocation', () => {
    const rules: CustomRule[] = [
      {
        name: 'block-npm-global',
        command: 'npm',
        subcommand: undefined,
        block_args: ['-g', '--global'],
        reason: 'No global installs.',
      },
    ];
    // Match with install subcommand
    let result = checkCustomRules(['npm', 'install', '-g', 'pkg'], rules);
    expect(result).toBe('[block-npm-global] No global installs.');

    // Match with uninstall subcommand too
    result = checkCustomRules(['npm', 'uninstall', '-g', 'pkg'], rules);
    expect(result).toBe('[block-npm-global] No global installs.');
  });

  test('multiple rules first match wins', () => {
    const rules: CustomRule[] = [
      {
        name: 'rule1',
        command: 'git',
        subcommand: 'add',
        block_args: ['-A'],
        reason: 'Rule 1 reason',
      },
      {
        name: 'rule2',
        command: 'git',
        subcommand: 'add',
        block_args: ['-A'],
        reason: 'Rule 2 reason',
      },
    ];
    const result = checkCustomRules(['git', 'add', '-A'], rules);
    expect(result).toBe('[rule1] Rule 1 reason');
  });

  test('case sensitive command matching', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: undefined,
        block_args: ['-A'],
        reason: 'test',
      },
    ];
    // Lowercase git matches
    let result = checkCustomRules(['git', '-A'], rules);
    expect(result).toBe('[test] test');

    // Uppercase GIT does NOT match (case-sensitive)
    result = checkCustomRules(['GIT', '-A'], rules);
    expect(result).toBeNull();
  });

  test('case sensitive arg matching', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: undefined,
        block_args: ['-A'],
        reason: 'test',
      },
    ];
    // -A matches
    let result = checkCustomRules(['git', '-A'], rules);
    expect(result).not.toBeNull();

    // -a does NOT match
    result = checkCustomRules(['git', '-a'], rules);
    expect(result).toBeNull();
  });

  test('args with values can be matched', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'docker',
        subcommand: 'run',
        block_args: ['--privileged'],
        reason: 'No privileged mode.',
      },
    ];
    const result = checkCustomRules(['docker', 'run', '--privileged', 'image'], rules);
    expect(result).toBe('[test] No privileged mode.');
  });

  test('subcommand with options before - git -C handled correctly', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: 'push',
        block_args: ['--force'],
        reason: 'No force push.',
      },
    ];
    // git -C /path push --force: correctly identifies push as subcommand
    let result = checkCustomRules(['git', '-C', '/path', 'push', '--force'], rules);
    expect(result).toBe('[test] No force push.');

    // Attached form -C/path also works
    result = checkCustomRules(['git', '-C/path', 'push', '--force'], rules);
    expect(result).toBe('[test] No force push.');
  });

  test('docker compose pattern', () => {
    const rules: CustomRule[] = [
      {
        name: 'block-docker-compose-up',
        command: 'docker',
        subcommand: 'compose',
        block_args: ['up'],
        reason: 'No docker compose up.',
      },
    ];
    const result = checkCustomRules(['docker', 'compose', 'up', '-d'], rules);
    expect(result).toBe('[block-docker-compose-up] No docker compose up.');
  });

  test('empty tokens returns null', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: undefined,
        block_args: ['-A'],
        reason: 'test',
      },
    ];
    const result = checkCustomRules([], rules);
    expect(result).toBeNull();
  });

  test('empty rules returns null', () => {
    const result = checkCustomRules(['git', 'add', '-A'], []);
    expect(result).toBeNull();
  });

  test('command with path normalized', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: undefined,
        block_args: ['-A'],
        reason: 'test',
      },
    ];
    const result = checkCustomRules(['/usr/bin/git', '-A'], rules);
    expect(result).toBe('[test] test');
  });

  test('block args with equals value', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'npm',
        subcommand: 'config',
        block_args: ['--location=global'],
        reason: 'No global config.',
      },
    ];
    const tokens = ['npm', 'config', 'set', '--location=global'];
    const result = checkCustomRules(tokens, rules);
    expect(result).toBe('[test] No global config.');
  });

  test('block dot for git add', () => {
    const rules: CustomRule[] = [
      {
        name: 'block-git-add-dot',
        command: 'git',
        subcommand: 'add',
        block_args: ['.'],
        reason: 'Use specific files.',
      },
    ];
    let result = checkCustomRules(['git', 'add', '.'], rules);
    expect(result).toBe('[block-git-add-dot] Use specific files.');

    // git add file.txt should pass
    result = checkCustomRules(['git', 'add', 'file.txt'], rules);
    expect(result).toBeNull();
  });

  test('multiple blocked args any matches', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: 'add',
        block_args: ['-A', '--all', '.', '-u'],
        reason: 'No blanket add.',
      },
    ];
    // Each blocked arg should trigger
    for (const arg of ['-A', '--all', '.', '-u']) {
      const result = checkCustomRules(['git', 'add', arg], rules);
      expect(result).not.toBeNull();
    }
  });

  test('combined short options expanded', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: 'add',
        block_args: ['-A'],
        reason: 'test',
      },
    ];
    // -Ap contains -A, so it should be blocked
    const result = checkCustomRules(['git', 'add', '-Ap'], rules);
    expect(result).toBe('[test] test');
  });

  test('combined short options case sensitive', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: 'add',
        block_args: ['-A'],
        reason: 'test',
      },
    ];
    // -ap does NOT contain -A (lowercase a != uppercase A)
    const result = checkCustomRules(['git', 'add', '-ap'], rules);
    expect(result).toBeNull();
  });

  test('combined short options multiple flags', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: 'add',
        block_args: ['-u'],
        reason: 'test',
      },
    ];
    // -Aup contains -u
    const result = checkCustomRules(['git', 'add', '-Aup'], rules);
    expect(result).toBe('[test] test');
  });

  test('long options not expanded', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: 'add',
        block_args: ['--all'],
        reason: 'test',
      },
    ];
    // --all-files is not --all
    const result = checkCustomRules(['git', 'add', '--all-files'], rules);
    expect(result).toBeNull();
  });

  test('subcommand after double dash', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: 'checkout',
        block_args: ['--force'],
        reason: 'test',
      },
    ];
    // git -- checkout --force: subcommand is checkout after --
    const result = checkCustomRules(['git', '--', 'checkout', '--force'], rules);
    expect(result).toBe('[test] test');
  });

  test('no subcommand after double dash at end', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: 'push',
        block_args: ['--force'],
        reason: 'test',
      },
    ];
    const result = checkCustomRules(['git', '--'], rules);
    expect(result).toBeNull();
  });

  test('long option with equals', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: 'push',
        block_args: ['--force'],
        reason: 'test',
      },
    ];
    const result = checkCustomRules(['git', '--config=foo', 'push', '--force'], rules);
    expect(result).toBe('[test] test');
  });

  test('long option without equals', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: 'push',
        block_args: ['--force'],
        reason: 'test',
      },
    ];
    // --verbose is a flag, push is subcommand
    const result = checkCustomRules(['git', '--verbose', 'push', '--force'], rules);
    expect(result).toBe('[test] test');
  });

  test('attached short option value', () => {
    const rules: CustomRule[] = [
      {
        name: 'test',
        command: 'git',
        subcommand: 'push',
        block_args: ['--force'],
        reason: 'test',
      },
    ];
    // -C/path is attached, so push is next
    const result = checkCustomRules(['git', '-C/path', 'push', '--force'], rules);
    expect(result).toBe('[test] test');
  });
});
