import { RuleTester } from 'eslint';
import tseslint from 'typescript-eslint';
import test from 'node:test';
import rule from '../../src/eslint/rules/webhook-json-parse-must-have-try.js';

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

const tsRuleTester = new RuleTester({
  languageOptions: {
    parser: tseslint.parser,
    ecmaVersion: 2022,
    sourceType: 'module',
  },
});

const WEBHOOK_FILE = '/repo/integrations/foo/src/webhook.ts';
const WEBHOOKS_FILE = '/repo/integrations/foo/src/webhooks.ts';
const PARSE_WEBHOOKS_FILE = '/repo/integrations/foo/src/parseWebhooks.ts';
const WEBHOOK_HELPER_FILE = '/repo/integrations/foo/src/webhookHelper.ts';
const WEBHOOKS_HELPER_FILE = '/repo/integrations/foo/src/helpers/webhooksHelper.ts';
const NON_WEBHOOK_FILE = '/repo/integrations/foo/src/handlers/item.ts';
const PARSE_EVENTS_FILE = '/repo/integrations/foo/src/handlers/parseEvents.ts';
const EVENT_DISPATCHER_FILE = '/repo/integrations/foo/src/handlers/eventDispatcher.ts';

test('webhook-json-parse-must-have-try', () => {
  ruleTester.run('webhook-json-parse-must-have-try', rule, {
    valid: [
      {
        name: 'try-wrapped JSON.parse in webhook.ts',
        filename: WEBHOOK_FILE,
        code: `function f(raw) { try { return JSON.parse(raw); } catch (e) { return null; } }`,
      },
      {
        name: 'try-wrapped JSON.parse with logger in webhooks.ts',
        filename: WEBHOOKS_FILE,
        code: `function f(raw) { try { const body = JSON.parse(raw); return body; } catch (e) { logger.warn('bad', { error: e }); return null; } }`,
      },
      {
        name: 'JSON.parse inside catch handler is fine (already error-handling)',
        filename: WEBHOOK_FILE,
        code: `function f(raw) { try { doThing(); } catch (e) { const fallback = JSON.parse('{}'); return fallback; } }`,
      },
      {
        name: 'JSON.parse inside arrow inside try.block is covered',
        filename: PARSE_WEBHOOKS_FILE,
        code: `function f(raw) { try { return [raw].map((r) => JSON.parse(r)); } catch (e) { return []; } }`,
      },
      {
        name: 'JSON.parse in non-webhook file is skipped',
        filename: NON_WEBHOOK_FILE,
        code: `function f(raw) { return JSON.parse(raw); }`,
      },
      {
        name: 'webhookHelper.ts with try-wrapped JSON.parse',
        filename: WEBHOOK_HELPER_FILE,
        code: `function parsePayload(raw) { try { return JSON.parse(raw); } catch (e) { logger.warn('x'); return null; } }`,
      },
      {
        name: 'webhooksHelper.ts with try-wrapped JSON.parse',
        filename: WEBHOOKS_HELPER_FILE,
        code: `export function parse(s) { try { return JSON.parse(s); } catch { return undefined; } }`,
      },
      {
        name: 'no JSON.parse at all in webhook file',
        filename: WEBHOOK_FILE,
        code: `function f(body) { return body.id; }`,
      },
      {
        name: 'JSON.stringify is not flagged',
        filename: WEBHOOK_FILE,
        code: `function f(body) { return JSON.stringify(body); }`,
      },
      {
        name: 'unrelated parse method on non-JSON object is not flagged',
        filename: WEBHOOK_FILE,
        code: `function f(s) { return Date.parse(s); }`,
      },
    ],
    invalid: [
      {
        name: 'bare JSON.parse in webhook.ts',
        filename: WEBHOOK_FILE,
        code: `function f(raw) { return JSON.parse(raw); }`,
        errors: [{ messageId: 'missingTry' }],
      },
      {
        name: 'bare JSON.parse in webhooks.ts',
        filename: WEBHOOKS_FILE,
        code: `function f(raw) { const body = JSON.parse(raw); return body; }`,
        errors: [{ messageId: 'missingTry' }],
      },
      {
        name: 'bare JSON.parse in parseWebhooks.ts',
        filename: PARSE_WEBHOOKS_FILE,
        code: `export function parse(raw) { return JSON.parse(raw); }`,
        errors: [{ messageId: 'missingTry' }],
      },
      {
        name: 'bare JSON.parse in webhookHelper.ts',
        filename: WEBHOOK_HELPER_FILE,
        code: `function parsePayload(raw) { const data = JSON.parse(raw); return data; }`,
        errors: [{ messageId: 'missingTry' }],
      },
      {
        name: 'bare JSON.parse in webhooksHelper.ts',
        filename: WEBHOOKS_HELPER_FILE,
        code: `export function parse(s) { return JSON.parse(s); }`,
        errors: [{ messageId: 'missingTry' }],
      },
      {
        name: 'JSON.parse inside arrow function nested in webhook function — function boundary crosses before try',
        filename: WEBHOOK_FILE,
        code: `function f(items) { return items.map((r) => JSON.parse(r)); }`,
        errors: [{ messageId: 'missingTry' }],
      },
      {
        name: 'JSON.parse before an unrelated try — try does not cover the parse',
        filename: WEBHOOK_FILE,
        code: `function f(raw) { const x = JSON.parse(raw); try { doIt(); } catch (e) { log(e); } return x; }`,
        errors: [{ messageId: 'missingTry' }],
      },
      {
        name: 'JSON.parse at module top-level (no enclosing function or try)',
        filename: WEBHOOK_FILE,
        code: `const config = JSON.parse(process.env.CFG);`,
        errors: [{ messageId: 'missingTry' }],
      },
      {
        name: 'bracket-access JSON["parse"](raw) is also flagged',
        filename: WEBHOOK_FILE,
        code: `function f(raw) { return JSON['parse'](raw); }`,
        errors: [{ messageId: 'missingTry' }],
      },
    ],
  });
});

test('webhook-json-parse-must-have-try — handler-type AST detection', () => {
  tsRuleTester.run('webhook-json-parse-must-have-try', rule, {
    valid: [
      {
        name: 'ParseWebhooksHandler in non-webhook file with try-wrapped JSON.parse',
        filename: PARSE_EVENTS_FILE,
        code: `
          import { ParseWebhooksHandler } from '@unito/integration-sdk';
          export const parseEvents: ParseWebhooksHandler = async (ctx) => {
            try { return JSON.parse(ctx.body.payload); } catch (e) { return []; }
          };
        `,
      },
      {
        name: 'AcknowledgeWebhooksHandler in non-webhook file with try-wrapped JSON.parse',
        filename: EVENT_DISPATCHER_FILE,
        code: `
          import { AcknowledgeWebhooksHandler } from '@unito/integration-sdk';
          export const ack: AcknowledgeWebhooksHandler = async (ctx) => {
            try { const p = JSON.parse(ctx.body.payload); return { statusCode: 200 }; } catch { return { statusCode: 400 }; }
          };
        `,
      },
      {
        name: 'non-handler-typed function in non-webhook file with bare JSON.parse is skipped',
        filename: NON_WEBHOOK_FILE,
        code: `
          export const handler = async (ctx: { body: { payload: string } }) => {
            return JSON.parse(ctx.body.payload);
          };
        `,
      },
      {
        name: 'unrelated typed export in non-webhook file with bare JSON.parse is skipped',
        filename: NON_WEBHOOK_FILE,
        code: `
          type SomeOtherHandler = (s: string) => unknown;
          export const handler: SomeOtherHandler = (s) => JSON.parse(s);
        `,
      },
    ],
    invalid: [
      {
        name: 'ParseWebhooksHandler in non-webhook file with bare JSON.parse — flagged via AST signal',
        filename: PARSE_EVENTS_FILE,
        code: `
          import { ParseWebhooksHandler } from '@unito/integration-sdk';
          export const parseEvents: ParseWebhooksHandler = async (ctx) => {
            const body = JSON.parse(ctx.body.payload);
            return body;
          };
        `,
        errors: [{ messageId: 'missingTry' }],
      },
      {
        name: 'AcknowledgeWebhooksHandler in non-webhook file with bare JSON.parse — flagged via AST signal',
        filename: EVENT_DISPATCHER_FILE,
        code: `
          import { AcknowledgeWebhooksHandler } from '@unito/integration-sdk';
          export const ack: AcknowledgeWebhooksHandler = async (ctx) => {
            const p = JSON.parse(ctx.body.payload);
            return { statusCode: 200, payload: JSON.stringify(p) };
          };
        `,
        errors: [{ messageId: 'missingTry' }],
      },
      {
        name: 'singular ParseWebhookHandler variant also flagged',
        filename: PARSE_EVENTS_FILE,
        code: `
          import { ParseWebhookHandler } from '@unito/integration-sdk';
          export const parseEvent: ParseWebhookHandler = async (ctx) => JSON.parse(ctx.body.payload);
        `,
        errors: [{ messageId: 'missingTry' }],
      },
    ],
  });
});
