import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { assertAllowed, assertBlocked, runGuard, withEnv } from './helpers.ts';

describe('edge cases', () => {
  let tempDir: string;

  beforeEach(() => {
    tempDir = mkdtempSync(join(tmpdir(), 'safety-net-test-'));
  });

  afterEach(() => {
    rmSync(tempDir, { recursive: true, force: true });
  });

  describe('input validation', () => {
    test('empty command allows', () => {
      assertAllowed('');
    });

    test('whitespace command allows', () => {
      assertAllowed('   ');
    });

    test('case insensitive matching blocks', () => {
      assertBlocked('GIT CHECKOUT -- file', 'git checkout --');
    });
  });

  describe('strict mode', () => {
    test('strict mode parse error denies', () => {
      withEnv({ SAFETY_NET_STRICT: '1' }, () => {
        const result = runGuard("git reset --hard 'unterminated");
        expect(result).not.toBeNull();
      });
    });

    test('strict mode unparseable safe command denies', () => {
      withEnv({ SAFETY_NET_STRICT: '1' }, () => {
        const result = runGuard("echo 'unterminated");
        expect(result).not.toBeNull();
        expect(result).toContain('could not be safely analyzed');
      });
    });

    test('non-strict mode unparseable safe command allows', () => {
      assertAllowed("echo 'unterminated");
    });

    test('strict mode bash -c without arg allows', () => {
      withEnv({ SAFETY_NET_STRICT: '1' }, () => {
        assertAllowed('bash -c');
      });
    });

    test('non strict bash -c without arg allows', () => {
      assertAllowed('bash -c');
    });

    test('bash double dash does not treat dash c as wrapper allowed', () => {
      assertAllowed("bash -- -c 'echo ok'");
    });

    test('strict mode bash without dash c allowed', () => {
      withEnv({ SAFETY_NET_STRICT: '1' }, () => {
        assertAllowed('bash -l echo ok');
      });
    });

    test('strict mode bash only allowed', () => {
      withEnv({ SAFETY_NET_STRICT: '1' }, () => {
        assertAllowed('bash');
      });
    });

    test('strict mode bash double dash does not treat dash c as wrapper allowed', () => {
      withEnv({ SAFETY_NET_STRICT: '1' }, () => {
        assertAllowed("bash -- -c 'echo ok'");
      });
    });

    test('strict mode python without one liner allowed', () => {
      withEnv({ SAFETY_NET_STRICT: '1' }, () => {
        assertAllowed('python script.py');
      });
    });

    test('strict mode python double dash does not treat dash c as one liner allowed', () => {
      withEnv({ SAFETY_NET_STRICT: '1' }, () => {
        assertAllowed("python -- -c 'print(1)'");
      });
    });

    test('strict mode python one liner allowed', () => {
      withEnv({ SAFETY_NET_STRICT: '1' }, () => {
        assertAllowed('python -c "print(\'ok\')"');
      });
    });

    test('strict mode bash lc without arg allows', () => {
      withEnv({ SAFETY_NET_STRICT: '1' }, () => {
        assertAllowed('bash -lc');
      });
    });
  });

  describe('shell wrappers', () => {
    test('sh -lc wrapper blocked', () => {
      assertBlocked("sh -lc 'git reset --hard'", 'git reset --hard');
    });
  });

  describe('unparseable commands with heuristics', () => {
    test('non strict unparseable rm -rf still blocked by heuristic', () => {
      assertBlocked("rm -rf /some/path 'unterminated", 'rm -rf');
    });

    test('non strict unparseable git push -f still blocked by heuristic', () => {
      assertBlocked("git push -f origin main 'unterminated", 'push --force');
    });

    test('non strict unparseable find delete blocked by heuristic', () => {
      assertBlocked("find . -delete 'unterminated", 'find -delete');
    });

    test('non strict unparseable non dangerous allows', () => {
      assertAllowed("echo 'unterminated");
    });

    test('non strict unparseable git restore help allows', () => {
      assertAllowed("git restore --help 'unterminated");
    });

    test('non strict unparseable git checkout dash dash still blocked by heuristic', () => {
      assertBlocked("git checkout -- file.txt 'unterminated", 'git checkout --');
    });

    test('non strict unparseable git restore blocked by heuristic', () => {
      assertBlocked("git restore file.txt 'unterminated", 'git restore');
    });

    test('non strict unparseable git restore worktree still blocked by heuristic', () => {
      assertBlocked("git restore --worktree file.txt 'unterminated", 'git restore');
    });

    test('non strict unparseable git stash clear still blocked by heuristic', () => {
      assertBlocked("git stash clear 'unterminated", 'git stash drop/clear');
    });

    test('non strict unparseable git branch D still blocked by heuristic', () => {
      assertBlocked("git branch -D feature 'unterminated", 'git branch -D');
    });

    test('non strict unparseable git reset hard still blocked by heuristic', () => {
      assertBlocked("git reset --hard 'unterminated", 'git reset --hard');
    });

    test('non strict unparseable git reset merge still blocked by heuristic', () => {
      assertBlocked("git reset --merge 'unterminated", 'git reset --merge');
    });

    test('non strict unparseable git clean f still blocked by heuristic', () => {
      assertBlocked("git clean -f 'unterminated", 'git clean -f');
    });

    test('non strict unparseable git stash drop still blocked by heuristic', () => {
      assertBlocked("git stash drop stash@{0} 'unterminated", 'git stash drop');
    });

    test('non strict unparseable git push force still blocked by heuristic', () => {
      assertBlocked("git push --force origin main 'unterminated", 'push --force');
    });

    test('unparseable echo mentions find delete allowed', () => {
      assertAllowed('echo "find . -delete');
    });

    test('unparseable rg mentions find delete allowed', () => {
      assertAllowed('rg "find . -delete');
    });
  });

  describe('command substitution', () => {
    test('command substitution git reset hard blocked', () => {
      assertBlocked('echo $(git reset --hard )', 'git reset --hard');
    });

    test('command substitution find delete blocked', () => {
      assertBlocked('echo $(find . -delete )', 'find -delete');
    });

    test('command substitution rm f allowed', () => {
      assertAllowed('echo $(rm -f /tmp/a )');
    });

    test('command substitution git status allowed', () => {
      assertAllowed('echo $(git status )');
    });

    test('command substitution find without delete allowed', () => {
      assertAllowed('echo $(find . -name foo )');
    });
  });

  describe('xargs', () => {
    test('xargs rm -rf blocked', () => {
      assertBlocked('echo / | xargs rm -rf', 'rm -rf');
    });

    test('xargs delimiter option still blocks child rm', () => {
      assertBlocked("echo / | xargs --delimiter '\\n' rm -rf", 'rm -rf');
    });

    test('xargs dash i does not consume child cmd still blocks', () => {
      assertBlocked('echo / | xargs -i rm -rf', 'rm -rf');
    });

    test('xargs attached n option still blocks child rm', () => {
      assertBlocked('echo / | xargs -n1 rm -rf', 'rm -rf');
    });

    test('xargs attached P option still blocks child rm', () => {
      assertBlocked('echo / | xargs -P2 rm -rf', 'rm -rf');
    });

    test('xargs long opt equals still blocks child rm', () => {
      assertBlocked('echo / | xargs --arg-file=/tmp/paths rm -rf', 'rm -rf');
    });

    test('xargs only options without child command allowed', () => {
      assertAllowed('echo ok | xargs -n1');
    });

    test('xargs attached i option still blocks child rm', () => {
      assertBlocked('echo / | xargs -i{} rm -rf', 'rm -rf');
    });

    test('xargs bash c script analyzed blocks', () => {
      assertBlocked("echo ok | xargs bash -c 'git reset --hard'", 'xargs');
    });

    test('xargs child wrappers only allowed', () => {
      assertAllowed('echo ok | xargs sudo --');
    });

    test('xargs busybox rm non destructive allowed', () => {
      assertAllowed('echo ok | xargs busybox rm -f /tmp/test');
    });

    test('xargs find without delete allowed', () => {
      assertAllowed('echo ok | xargs find . -name foo');
    });

    test('xargs print0 rm -rf blocked', () => {
      assertBlocked('find . -print0 | xargs -0 rm -rf', 'rm -rf');
    });

    test('xargs arg file option still blocks child rm', () => {
      assertBlocked('echo ok | xargs -a /tmp/paths rm -rf', 'rm -rf');
    });

    test('xargs echo allowed', () => {
      assertAllowed('echo ok | xargs echo');
    });

    test('xargs busybox rm -rf blocked', () => {
      assertBlocked('echo / | xargs busybox rm -rf', 'rm -rf');
    });

    test('xargs busybox find delete blocked', () => {
      assertBlocked('echo ok | xargs busybox find . -delete', 'find -delete');
    });

    test('xargs without child command allowed', () => {
      assertAllowed('echo ok | xargs');
    });

    test('xargs find delete blocked', () => {
      assertBlocked('echo ok | xargs find . -delete', 'find -delete');
    });

    test('xargs git reset hard blocked', () => {
      assertBlocked('echo ok | xargs git reset --hard', 'git reset --hard');
    });

    test('xargs replace I rm rf blocked', () => {
      assertBlocked('echo / | xargs -I{} rm -rf {}', 'xargs', tempDir);
    });

    test('xargs replace long option enables placeholder analysis', () => {
      assertBlocked("echo / | xargs --replace bash -c 'rm -rf {}'", 'xargs');
    });

    test('xargs replace long option with custom token enables placeholder analysis', () => {
      assertBlocked("echo / | xargs --replace=FOO bash -c 'rm -rf FOO'", 'xargs');
    });

    test('xargs replace long option empty value defaults to braces', () => {
      assertBlocked("echo / | xargs --replace= bash -c 'rm -rf {}'", 'xargs');
    });

    test('xargs replacement token parsing ignores unknown options', () => {
      assertBlocked("echo / | xargs --replace -t bash -c 'rm -rf {}'", 'xargs');
    });

    test('xargs replace I bash c script is input denied safe input', () => {
      assertBlocked('echo ok | xargs -I{} bash -c {}', 'arbitrary');
    });

    test('xargs bash c without arg denied safe input', () => {
      assertBlocked('echo ok | xargs bash -c', 'arbitrary');
    });

    test('xargs replace I bash c placeholder rm rf blocked', () => {
      assertBlocked("echo / | xargs -I{} bash -c 'rm -rf {}'", 'xargs', tempDir);
    });

    test('xargs replace custom token bash c placeholder rm rf blocked', () => {
      assertBlocked("echo / | xargs -I% bash -c 'rm -rf %'", 'xargs', tempDir);
    });

    test('xargs replace I bash c script is input denied', () => {
      assertBlocked("echo 'rm -rf /' | xargs -I{} bash -c {}", 'xargs');
    });

    test('xargs J consumes value still blocks child rm', () => {
      assertBlocked('echo / | xargs -J {} rm -rf {}', 'rm -rf');
    });

    test('xargs rm double dash prevents dash rf as option allowed', () => {
      assertAllowed('echo ok | xargs rm -- -rf', tempDir);
    });

    test('xargs bash c dynamic denied', () => {
      assertBlocked("echo 'rm -rf /' | xargs bash -c", 'xargs');
    });
  });

  describe('parallel', () => {
    test('parallel bash c dynamic denied', () => {
      assertBlocked("parallel bash -c ::: 'rm -rf /'", 'parallel');
    });

    test('parallel stdin mode blocks rm -rf', () => {
      assertBlocked('echo / | parallel rm -rf', 'rm -rf');
    });

    test('parallel busybox stdin mode blocks rm -rf', () => {
      assertBlocked('echo / | parallel busybox rm -rf', 'rm -rf');
    });

    test('parallel busybox find delete blocked', () => {
      assertBlocked('parallel busybox find . -delete ::: ok', 'find -delete');
    });

    test('parallel git reset hard blocked', () => {
      assertBlocked('parallel git reset --hard ::: ok', 'git reset --hard');
    });

    test('parallel find delete blocked', () => {
      assertBlocked('parallel find . -delete ::: ok', 'find -delete');
    });

    test('parallel find without delete allowed', () => {
      assertAllowed('parallel find . -name foo ::: ok');
    });

    test('parallel busybox find without delete allowed', () => {
      assertAllowed('parallel busybox find . -name foo ::: ok');
    });

    test('parallel stdin without template allowed', () => {
      assertAllowed('echo ok | parallel');
    });

    test('parallel marker without template allowed', () => {
      assertAllowed('parallel :::');
    });

    test('parallel bash c script is input denied', () => {
      assertBlocked("echo 'rm -rf /' | parallel bash -c {}", 'parallel');
    });

    test('parallel bash c script is input denied safe input', () => {
      assertBlocked('echo ok | parallel bash -c {}', 'arbitrary');
    });

    test('parallel results option blocks rm rf', () => {
      assertBlocked('parallel --results out rm -rf {} ::: /', 'rm -rf', tempDir);
    });

    test('parallel jobs attached option blocks', () => {
      assertBlocked('parallel -j2 rm -rf {} ::: /', 'root or home', tempDir);
    });

    test('parallel jobs long equals option blocks', () => {
      assertBlocked('parallel --jobs=2 rm -rf {} ::: /', 'root or home', tempDir);
    });

    test('parallel unknown long option is ignored for template parsing', () => {
      assertBlocked('parallel --eta rm -rf {} ::: /', 'root or home', tempDir);
    });

    test('parallel unknown short option ignored for template parsing', () => {
      assertBlocked('parallel -q rm -rf {} ::: /', 'root or home', tempDir);
    });

    test('parallel bash c stdin mode blocks rm rf placeholder', () => {
      assertBlocked("echo / | parallel bash -c 'rm -rf {}'", 'rm -rf');
    });

    test('parallel commands mode blocks rm rf', () => {
      assertBlocked("parallel ::: 'rm -rf /'", 'rm -rf');
    });

    test('parallel commands mode allows when all commands safe', () => {
      assertAllowed("parallel ::: 'echo ok' 'true'");
    });

    test('parallel rm rf args after marker without placeholder blocked', () => {
      assertBlocked('parallel rm -rf ::: /', 'root or home');
    });

    test('parallel rm rf with replacement args analyzed', () => {
      assertBlocked('parallel rm -rf {} ::: /', 'rm -rf', tempDir);
    });

    test('parallel bash c rm rf with replacement args analyzed', () => {
      assertBlocked("parallel bash -c 'rm -rf {}' ::: /", 'rm -rf', tempDir);
    });

    test('parallel busybox rm rf with replacement args analyzed', () => {
      assertBlocked('parallel busybox rm -rf {} ::: /', 'rm -rf', tempDir);
    });

    test('parallel rm rf with safe replacement allowed', () => {
      assertAllowed('parallel rm -rf {} ::: build', tempDir);
    });

    test('parallel bash c rm rf with safe replacement allowed', () => {
      assertAllowed("parallel bash -c 'rm -rf {}' ::: build", tempDir);
    });

    test('parallel busybox rm rf with safe replacement allowed', () => {
      assertAllowed('parallel busybox rm -rf {} ::: build', tempDir);
    });

    test('parallel bash c without placeholder analyzes script', () => {
      assertBlocked("parallel bash -c 'git reset --hard' ::: ok", 'git reset --hard');
    });

    test('parallel bash c without placeholder allows safe script', () => {
      assertAllowed("parallel bash -c 'echo ok' ::: ok");
    });

    test('parallel busybox rm rf args after marker without placeholder blocked', () => {
      assertBlocked('parallel busybox rm -rf ::: /', 'root or home');
    });
  });

  describe('busybox', () => {
    test('busybox find delete blocked', () => {
      assertBlocked('busybox find . -delete', 'find -delete');
    });

    test('busybox find without delete allowed', () => {
      assertAllowed('busybox find . -name foo');
    });
  });

  describe('operators', () => {
    test('or operator split blocked', () => {
      assertBlocked('git status || git reset --hard', 'git reset --hard');
    });

    test('semicolon split blocked', () => {
      assertBlocked('git status; git reset --hard', 'git reset --hard');
    });

    test('newline split blocked', () => {
      assertBlocked('git status\ngit reset --hard', 'git reset --hard');
    });

    test('redirection ampersand does not split blocked', () => {
      assertBlocked('echo ok 2>&1 && git reset --hard', 'git reset --hard');
    });

    test('redirection ampersand greater does not split blocked', () => {
      assertBlocked('echo ok &>out && git reset --hard', 'git reset --hard');
    });

    test('pipe stderr and stdout split blocked', () => {
      assertBlocked('echo ok |& git reset --hard', 'git reset --hard');
    });

    test('shell split with leading operator still blocks', () => {
      assertBlocked('&& git reset --hard', 'git reset --hard');
    });

    test('shell split with leading pipe still blocks', () => {
      assertBlocked('| git reset --hard', 'git reset --hard');
    });

    test('shell split with leading pipe stderr operator still blocks', () => {
      assertBlocked('|& git reset --hard', 'git reset --hard');
    });

    test('shell split with leading background operator still blocks', () => {
      assertBlocked('& git reset --hard', 'git reset --hard');
    });

    test('shell split with trailing separator ignored allowed', () => {
      assertAllowed('git status;');
    });
  });

  describe('env wrappers', () => {
    test('sudo double dash wrapper bypass blocked', () => {
      assertBlocked('sudo -- git reset --hard', 'git reset --hard');
    });

    test('env unset equals wrapper bypass blocked', () => {
      assertBlocked('env --unset=PATH git reset --hard', 'git reset --hard');
    });

    test('env unset attached wrapper bypass blocked', () => {
      assertBlocked('env -uPATH git reset --hard', 'git reset --hard');
    });

    test('env C attached wrapper bypass blocked', () => {
      assertBlocked('env -C/tmp git reset --hard', 'git reset --hard');
    });

    test('env C separate wrapper bypass blocked', () => {
      assertBlocked('env -C /tmp git reset --hard', 'git reset --hard');
    });

    test('env P wrapper bypass blocked', () => {
      assertBlocked('env -P /usr/bin git reset --hard', 'git reset --hard');
    });

    test('env S wrapper bypass blocked', () => {
      assertBlocked("env -S 'PATH=/usr/bin' git reset --hard", 'git reset --hard');
    });

    test('env dash breaks option scan still blocks', () => {
      assertBlocked('env - git reset --hard', 'git reset --hard');
    });

    test('command combined short opts wrapper bypass blocked', () => {
      assertBlocked('command -pv -- git reset --hard', 'git reset --hard');
    });

    test('command V wrapper bypass blocked', () => {
      assertBlocked('command -V git reset --hard', 'git reset --hard');
    });

    test('command combined short opts with V wrapper bypass blocked', () => {
      assertBlocked('command -pvV -- git reset --hard', 'git reset --hard');
    });

    test('env assignments stripped blocked', () => {
      assertBlocked('FOO=1 BAR=2 git reset --hard', 'git reset --hard');
    });

    test('invalid env assignment key does not strip still blocks', () => {
      assertBlocked('1A=2 git reset --hard', 'git reset --hard');
    });

    test('invalid env assignment chars does not strip still blocks', () => {
      assertBlocked('A-B=2 git reset --hard', 'git reset --hard');
    });

    test('empty env assignment key does not strip still blocks', () => {
      assertBlocked('=2 git reset --hard', 'git reset --hard');
    });

    test('only env assignments allowed', () => {
      assertAllowed('FOO=1');
    });

    test('sudo option wrapper bypass blocked', () => {
      assertBlocked('sudo -u root -- git reset --hard', 'git reset --hard');
    });

    test('env P attached wrapper bypass blocked', () => {
      assertBlocked('env -P/usr/bin git reset --hard', 'git reset --hard');
    });

    test('env S attached wrapper bypass blocked', () => {
      assertBlocked('env -SPATH=/usr/bin git reset --hard', 'git reset --hard');
    });

    test('env unknown option wrapper bypass blocked', () => {
      assertBlocked('env -i git reset --hard', 'git reset --hard');
    });

    test('command unknown short opts not stripped still blocks', () => {
      assertBlocked('command -px git reset --hard', 'git reset --hard');
    });
  });

  describe('interpreter one-liners', () => {
    test('node -e dangerous blocked', () => {
      assertBlocked('node -e "rm -rf /"', 'rm -rf');
    });

    test('node -e safe allowed', () => {
      assertAllowed('node -e "console.log(\\"ok\\")"');
    });

    test('ruby -e dangerous blocked', () => {
      assertBlocked('ruby -e "rm -rf /"', 'rm -rf');
    });

    test('ruby -e safe allowed', () => {
      assertAllowed('ruby -e "puts \'ok\'"');
    });

    test('perl -e dangerous blocked', () => {
      assertBlocked('perl -e "rm -rf /"', 'rm -rf');
    });

    test('perl -e safe allowed', () => {
      assertAllowed('perl -e "print \'ok\'"');
    });
  });

  describe('paranoid mode', () => {
    test('paranoid mode python one liner denies', () => {
      withEnv({ SAFETY_NET_PARANOID_INTERPRETERS: '1' }, () => {
        assertBlocked('python -c "print(\'ok\')"', 'Paranoid mode');
      });
    });

    test('global paranoid mode python one liner denies', () => {
      withEnv({ SAFETY_NET_PARANOID: '1' }, () => {
        assertBlocked('python -c "print(\'ok\')"', 'Paranoid mode');
      });
    });
  });

  describe('recursion', () => {
    test('shell dash c recursion limit reached blocks command', () => {
      let cmd = 'rm -rf /some/path';
      for (let i = 0; i < 11; i++) {
        cmd = `bash -c ${JSON.stringify(cmd)}`;
      }
      assertBlocked(cmd, 'recursion');
    });
  });

  describe('cwd handling', () => {
    test('cwd empty string treated as unknown', () => {
      assertBlocked('git reset --hard', 'git reset --hard', '');
    });
  });

  describe('display-only commands bypass fallback scanning', () => {
    test('echo with git reset --hard allowed', () => {
      assertAllowed('echo git reset --hard');
    });

    test('echo with rm -rf allowed', () => {
      assertAllowed('echo rm -rf /');
    });

    test('printf with git reset --hard allowed', () => {
      assertAllowed("printf 'git reset --hard'");
    });

    test('printf with rm -rf allowed', () => {
      assertAllowed("printf 'rm -rf /'");
    });

    test('cat with find -delete allowed', () => {
      assertAllowed('cat find -delete');
    });

    test('grep with git checkout -- file allowed', () => {
      assertAllowed("grep 'git checkout -- file' log.txt");
    });

    test('rg with rm -rf allowed', () => {
      assertAllowed("rg 'rm -rf' .");
    });

    test('sed with git reset --hard allowed', () => {
      assertAllowed("sed 's/git reset --hard/safe/' file.txt");
    });

    test('awk with rm -rf allowed', () => {
      assertAllowed("awk '/rm -rf/ {print}' log.txt");
    });

    test('head with git clean -f allowed', () => {
      assertAllowed('head git clean -f');
    });

    test('tail with git stash drop allowed', () => {
      assertAllowed('tail git stash drop');
    });

    test('wc with rm -rf allowed', () => {
      assertAllowed('wc rm -rf /');
    });

    test('less with git push --force allowed', () => {
      assertAllowed('less git push --force');
    });
  });

  describe('recursion depth boundary', () => {
    test('shell dash c recursion at exactly MAX_RECURSION_DEPTH (10) blocks', () => {
      let cmd = 'rm -rf /some/path';
      for (let i = 0; i < 10; i++) {
        cmd = `bash -c ${JSON.stringify(cmd)}`;
      }
      assertBlocked(cmd, 'recursion');
    });

    test('shell dash c recursion at depth 9 still blocks with rm reason', () => {
      let cmd = 'rm -rf /some/path';
      for (let i = 0; i < 9; i++) {
        cmd = `bash -c ${JSON.stringify(cmd)}`;
      }
      assertBlocked(cmd, 'rm -rf');
    });
  });

  describe('parallel rm placeholder expansion with mixed args', () => {
    test('parallel rm -rf with one safe and one dangerous arg blocked', () => {
      assertBlocked('parallel rm -rf {} ::: build /', 'rm -rf', tempDir);
    });

    test('parallel rm -rf with multiple dangerous args blocked', () => {
      assertBlocked('parallel rm -rf {} ::: / ~', 'rm -rf', tempDir);
    });

    test('parallel rm -rf with all safe args allowed', () => {
      assertAllowed('parallel rm -rf {} ::: build dist node_modules', tempDir);
    });

    test('parallel bash -c rm -rf with mixed args blocked', () => {
      assertBlocked("parallel bash -c 'rm -rf {}' ::: build /", 'rm -rf', tempDir);
    });
  });
});
