import { RuleTester } from 'eslint';
import test from 'node:test';
import rule from '../../src/eslint/rules/bad-request-only-for-input-validation.js';

const ruleTester = new RuleTester({
  languageOptions: {
    ecmaVersion: 2022,
    sourceType: 'module',
  },
});

const HANDLER_FILE = '/repo/integrations/foo/src/handlers/item.ts';

test('bad-request-only-for-input-validation', () => {
  ruleTester.run('bad-request-only-for-input-validation', rule, {
    valid: [
      {
        name: 'typed UnprocessableEntityError in catch is fine',
        filename: HANDLER_FILE,
        code: `async function f() { try { await provider.x(); } catch (e) { throw new HttpErrors.UnprocessableEntityError('boom'); } }`,
      },
      {
        name: 'BadRequestError in provider-catch but message starts with "Missing"',
        filename: HANDLER_FILE,
        code: `async function f() { try { await provider.x(); } catch (e) { throw new HttpErrors.BadRequestError('Missing required field "title"'); } }`,
      },
      {
        name: 'BadRequestError in provider-catch but message starts with "Invalid"',
        filename: HANDLER_FILE,
        code: `async function f() { try { await provider.x(); } catch (e) { throw new HttpErrors.BadRequestError('Invalid email format'); } }`,
      },
      {
        name: 'BadRequestError in provider-catch but message contains "required"',
        filename: HANDLER_FILE,
        code: `async function f() { try { await provider.x(); } catch (e) { throw new HttpErrors.BadRequestError('Field "title" is required'); } }`,
      },
      {
        name: 'BadRequestError in provider-catch but message contains "must be"',
        filename: HANDLER_FILE,
        code: `async function f() { try { await provider.x(); } catch (e) { throw new HttpErrors.BadRequestError('Status must be one of: open, closed'); } }`,
      },
      {
        name: 'BadRequestError thrown outside catch and not after provider await',
        filename: HANDLER_FILE,
        code: `function f(input) { if (!input) { throw new HttpErrors.BadRequestError('no good'); } }`,
      },
      {
        name: 'BadRequestError in provider.ts is allowlisted',
        filename: '/repo/integrations/foo/src/provider.ts',
        code: `async function f() { try { await provider.x(); } catch (e) { throw new HttpErrors.BadRequestError('boom'); } }`,
      },
      {
        name: 'BadRequestError in credentials.ts is allowlisted',
        filename: '/repo/integrations/foo/src/credentials.ts',
        code: `async function f() { try { await provider.x(); } catch (e) { throw new HttpErrors.BadRequestError('bad credentials'); } }`,
      },
      {
        name: 'BadRequestError in helpers/credentials.ts is allowlisted',
        filename: '/repo/integrations/foo/src/helpers/credentials.ts',
        code: `async function f() { try { await provider.x(); } catch (e) { throw new HttpErrors.BadRequestError('bad credentials'); } }`,
      },
      {
        name: 'BadRequestError after non-provider await is fine',
        filename: HANDLER_FILE,
        code: `async function f() { await db.query(); throw new HttpErrors.BadRequestError('boom'); }`,
      },
      {
        name: 'plain throw new Error is out of scope',
        filename: HANDLER_FILE,
        code: `async function f() { try { await provider.x(); } catch (e) { throw new Error('boom'); } }`,
      },
      {
        name: 'BadRequestError in catch wrapping JSON.parse is fine — sync input validation',
        filename: HANDLER_FILE,
        code: `function f(raw) { try { return JSON.parse(raw); } catch (e) { throw new HttpErrors.BadRequestError('Could not parse input'); } }`,
      },
      {
        name: 'BadRequestError in catch wrapping schema.validate is fine — sync input validation',
        filename: HANDLER_FILE,
        code: `function f(input) { try { schema.validate(input); } catch (e) { throw new HttpErrors.BadRequestError('Schema mismatch'); } }`,
      },
      {
        name: 'BadRequestError in catch wrapping a CallExpression on a non-provider helper',
        filename: HANDLER_FILE,
        code: `function f(s) { try { return parseInt(s, 10); } catch (e) { throw new HttpErrors.BadRequestError('Could not parse number'); } }`,
      },
    ],
    invalid: [
      {
        name: 'BadRequestError in catch wrapping `await provider.x()` with non-validation message',
        filename: HANDLER_FILE,
        code: `async function f() { try { await provider.x(); } catch (e) { throw new HttpErrors.BadRequestError('Upload failed'); } }`,
        errors: [{ messageId: 'badRequestNotInputValidation' }],
      },
      {
        name: 'BadRequestError in provider-catch with non-literal arg (cannot allowlist)',
        filename: HANDLER_FILE,
        code: `async function f() { try { await provider.x(); } catch (e) { throw new HttpErrors.BadRequestError(buildMsg(e)); } }`,
        errors: [{ messageId: 'badRequestNotInputValidation' }],
      },
      {
        name: 'BadRequestError immediately after `await provider.foo()`',
        filename: HANDLER_FILE,
        code: `async function f() { const r = await provider.fetchItem(); throw new HttpErrors.BadRequestError('something happened'); }`,
        errors: [{ messageId: 'badRequestNotInputValidation' }],
      },
      {
        name: 'BadRequestError 2 statements after `await provider.bar()`',
        filename: HANDLER_FILE,
        code: `async function f() { await provider.bar(); const x = 1; throw new HttpErrors.BadRequestError('failed'); }`,
        errors: [{ messageId: 'badRequestNotInputValidation' }],
      },
      {
        name: 'BadRequestError after `await Provider.thing()` (capitalized)',
        filename: HANDLER_FILE,
        code: `async function f() { await Provider.thing(); throw new HttpErrors.BadRequestError('failed'); }`,
        errors: [{ messageId: 'badRequestNotInputValidation' }],
      },
      {
        name: 'BadRequestError in provider-catch with template literal — provider message style',
        filename: HANDLER_FILE,
        code: 'async function f() { try { await provider.x(); } catch (e) { throw new HttpErrors.BadRequestError(`failed: ${e.message}`); } }',
        errors: [{ messageId: 'badRequestNotInputValidation' }],
      },
      {
        name: 'BadRequestError after `await provider.x()` with chained call',
        filename: HANDLER_FILE,
        code: `async function f() { await provider.api.fetch(); throw new HttpErrors.BadRequestError('boom'); }`,
        errors: [{ messageId: 'badRequestNotInputValidation' }],
      },
      {
        name: 'BadRequestError nested in if-block after provider await in outer block',
        filename: HANDLER_FILE,
        code: `async function f(y) { await provider.x(); if (y) { throw new HttpErrors.BadRequestError('boom'); } }`,
        errors: [{ messageId: 'badRequestNotInputValidation' }],
      },
      {
        name: 'BadRequestError in catch where async await is nested in if',
        filename: HANDLER_FILE,
        code: `async function f(y) { try { if (y) { await provider.x(); } } catch (e) { throw new HttpErrors.BadRequestError('failed'); } }`,
        errors: [{ messageId: 'badRequestNotInputValidation' }],
      },
      {
        name: 'BadRequestError in catch wrapping `await azureService.foo()` (service-helper)',
        filename: HANDLER_FILE,
        code: `async function f() { try { await azureService.listSubscriptions(); } catch (e) { throw new HttpErrors.BadRequestError('Failed to list webhooks'); } }`,
        errors: [{ messageId: 'badRequestNotInputValidation' }],
      },
      {
        name: 'BadRequestError in catch wrapping `await db.query()` — async I/O, prefer 422',
        filename: HANDLER_FILE,
        code: `async function f() { try { await db.query(); } catch (e) { throw new HttpErrors.BadRequestError('db unavailable'); } }`,
        errors: [{ messageId: 'badRequestNotInputValidation' }],
      },
    ],
  });
});
