import { RuleTester } from 'eslint';
import test from 'node:test';
import rule from '../../src/eslint/rules/no-error-message-rethrow-to-caller.js';

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

const HANDLER_FILE = '/repo/integrations/foo/src/handlers/record.ts';
const ROUTE_FILE = '/repo/integrations/foo/src/routes/comment.ts';
const RATE_LIMITER_FILE = '/repo/integrations/foo/src/handlers/rateLimiter.ts';
const NON_HANDLER_FILE = '/repo/integrations/foo/src/helpers/utils.ts';

const wrap = (body: string): string => `async function f() { ${body} }`;

test('no-error-message-rethrow-to-caller', () => {
  ruleTester.run('no-error-message-rethrow-to-caller', rule, {
    valid: [
      {
        name: 'static message in HttpErrors throw',
        filename: HANDLER_FILE,
        code: wrap(`
          try { foo(); } catch (error) {
            throw new HttpErrors.BadRequestError('Upload failed');
          }
        `),
      },
      {
        name: 'template literal with non-error context',
        filename: HANDLER_FILE,
        code: wrap(`
          try { foo(); } catch (error) {
            throw new HttpErrors.NotFoundError(\`item \${id} not found\`);
          }
        `),
      },
      {
        name: 'commit.message is not error context (different identifier)',
        filename: HANDLER_FILE,
        code: wrap(`
          try { foo(); } catch (error) {
            throw new HttpErrors.UnprocessableEntityError(commit.message);
          }
        `),
      },
      {
        name: 'error.status (not message) is fine',
        filename: HANDLER_FILE,
        code: wrap(`
          try { foo(); } catch (error) {
            throw new HttpErrors.X(\`status: \${error.status}\`);
          }
        `),
      },
      {
        name: 'logger.warn with error.message is fine — logger has its own redaction',
        filename: HANDLER_FILE,
        code: wrap(`
          try { foo(); } catch (error) {
            context.logger.warn('failed', { error: error.message });
            throw new HttpErrors.UnprocessableEntityError('Upload failed');
          }
        `),
      },
      {
        name: 'helpers/ file is out of scope (not handler/route)',
        filename: NON_HANDLER_FILE,
        code: wrap(`
          try { foo(); } catch (error) {
            throw new HttpErrors.BadRequestError(error.message);
          }
        `),
      },
      {
        name: 'rateLimiter.ts under handlers/ is allowlisted',
        filename: RATE_LIMITER_FILE,
        code: wrap(`
          try { foo(); } catch (error) {
            throw new HttpErrors.RateLimitExceededError(error.message);
          }
        `),
      },
      {
        name: 'throw outside catch is out of scope',
        filename: HANDLER_FILE,
        code: wrap(`
          if (cond) {
            throw new HttpErrors.BadRequestError(error.message);
          }
        `),
      },
      {
        name: 'throw inside catch but not HttpErrors',
        filename: HANDLER_FILE,
        code: wrap(`
          try { foo(); } catch (error) {
            throw new Error(error.message);
          }
        `),
      },
      {
        name: 'nested function inside catch — function boundary stops the scope walk',
        filename: HANDLER_FILE,
        code: wrap(`
          try { foo(); } catch (error) {
            const handler = () => {
              throw new HttpErrors.BadRequestError(error.message);
            };
            handler();
          }
        `),
      },
    ],
    invalid: [
      {
        name: 'direct error.message rethrow',
        filename: HANDLER_FILE,
        code: wrap(`
          try { foo(); } catch (error) {
            throw new HttpErrors.UnprocessableEntityError(error.message);
          }
        `),
        errors: [{ messageId: 'errorMessageRethrow' }],
      },
      {
        name: 'template literal interpolating err.message',
        filename: HANDLER_FILE,
        code: 'async function f() { try { foo(); } catch (err) { throw new HttpErrors.BadRequestError(`upload failed: ${err.message}`); } }',
        errors: [{ messageId: 'errorMessageRethrow' }],
      },
      {
        name: 'conditional ternary with error.message branch',
        filename: HANDLER_FILE,
        code: wrap(`
          try { foo(); } catch (error) {
            throw new HttpErrors.UnprocessableEntityError(error instanceof Error ? error.message : String(error));
          }
        `),
        errors: [{ messageId: 'errorMessageRethrow' }],
      },
      {
        name: 'mixed template w/ both status and message — message wins',
        filename: HANDLER_FILE,
        code: 'async function f() { try { foo(); } catch (error) { throw new HttpErrors.X(`status: ${error.status}, msg: ${error.message}`); } }',
        errors: [{ messageId: 'errorMessageRethrow' }],
      },
      {
        name: 'route file flagged the same as handler',
        filename: ROUTE_FILE,
        code: wrap(`
          try { foo(); } catch (e) {
            throw new HttpErrors.BadRequestError(\`failed: \${e.message}\`);
          }
        `),
        errors: [{ messageId: 'errorMessageRethrow' }],
      },
      {
        name: 'bracket-access error["message"] is also flagged',
        filename: HANDLER_FILE,
        code: wrap(`
          try { foo(); } catch (error) {
            throw new HttpErrors.BadRequestError(error['message']);
          }
        `),
        errors: [{ messageId: 'errorMessageRethrow' }],
      },
    ],
  });
});
