/**
 * Copyright IBM Corp. 2024, 2025
 */

import { ZodError } from 'zod';
import {
  validTest,
  validTestWithEndpoint,
  validTestWithEnvironmentsAndAssertions,
} from '../__mocks__/test-data/test.data.js';
import { TestSchema } from '../../src/schemas/test.schema.js';

jest.mock('@apic/api-model/common/Metadata.js', () => ({
  Metadata_TypeEnum: {
    REST: 'REST',
    SWAGGER: 'SWAGGER',
    SOAP: 'SOAP',
    GRAPHQL: 'GRAPHQL',
    ODATA: 'ODATA',
  },
}));

const validBase = {
  kind: 'test',
  metadata: {
    name: 'TestPayments',
    version: '1.0.0',
    tags: ['functional'],
    namespace: 'default',
  },
  spec: {
    api: {
      $ref: 'PaymentAPI:1.0.1',
    },
    environment: {
      $ref: 'default:TestPaymentsEnvironment:1.0.0',
    },
    request: [
      {
        method: 'POST',
        resource: 'v2/pet',
        headers: [
          {
            key: 'Content-Type',
            value: '${content-type}',
          },
        ],
        auth: {
          noauth: true,
        },
        payload: {
          raw: {
            json: '{\n  "name":"Jose"\n}\n',
          },
        },
        settings: {
          sslVerification: false,
          encodeURL: true,
        },
        assertions: {
          $ref: 'default:TestPaymentAssertion:1.0.0',
        },
      },
      {
        endpoint: 'new-url.com/api',
        method: '${method}',
        resource: 'v2/pet',
        var: [
          {
            record1: 'response[0]',
            record2: 'response[1]',
          },
        ],
        headers: [
          {
            key: 'Content-Type',
            value: '${content-type}',
          },
        ],
        auth: {
          noauth: true,
        },
        payload: {
          raw: {
            json: '{\n  "name":"Adam"\n}\n',
          },
        },
        settings: {
          sslVerification: false,
          encodeURL: true,
        },
        assertions: {
          $ref: 'default:TestPaymentAssertion:1.0.0',
        },
      },
    ],
  },
};

describe('TestSchema', () => {
  it('should pass with a minimal valid test', () => {
    expect(() => TestSchema.parse(validTest)).not.toThrow();
  });

  it('should pass with a minimal valid test', () => {
    expect(() => TestSchema.parse(validTestWithEndpoint)).not.toThrow();
  });

  it('should pass with a minimal valid test with multiple environments and assertions', () => {
    expect(() =>
      TestSchema.parse(validTestWithEnvironmentsAndAssertions),
    ).not.toThrow();
  });

  it("should fail if kind is not 'test'", () => {
    const input = { ...validTest, kind: 'something-else' };
    expect(() => TestSchema.parse(input)).toThrow();
    expect(() => TestSchema.parse(validBase)).not.toThrow();
  });

  it("should fail if kind is not 'test'", () => {
    const input = { ...validBase, kind: 'something-else' };
    expect(() => TestSchema.parse(input)).toThrow();
  });

  it('should fail if api is missing $ref and $endpoint', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        api: {},
      },
    };
    expect(() => TestSchema.parse(input)).toThrow(
      /Either \$ref or \$endpoint must be provided/,
    );
  });

  it('should pass with api using $ref', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        api: { $ref: '#/some/path' },
      },
    };
    expect(() => TestSchema.parse(input)).not.toThrow();
  });

  it('should pass with api using $ref as array', () => {
    const input = {
      ...validBase,
      spec: {
        ...validBase.spec,
        api: { $ref: ['#/some/path', '#/some/path2'] },
      },
    };
    expect(() => TestSchema.parse(input)).not.toThrow();
  });

  it('should pass with api using $endpoint', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        api: { $endpoint: '/some/path' },
      },
    };
    expect(() => TestSchema.parse(input)).not.toThrow();
  });

  it('should fail with api using $endpoint as array', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        api: { $endpoint: ['/some/path'] },
      },
    };
    expect(() => TestSchema.parse(input)).toThrow();
  });

  it('should fail with api using $ref and $endpoint', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        api: { $ref: '#/some/path', $endpoint: 'www.test.com' },
      },
    };
    try {
      TestSchema.parse(input);
    } catch (e: any) {
      expect(e).toBeInstanceOf(ZodError);
      expect(e.issues[0].message).toMatch(
        'Either $ref or $endpoint must be provided, but not both',
      );
    }
  });

  it('should pass with environment using $ref', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        environment: {
          $ref: '#/env/ref',
        },
      },
    };
    expect(() => TestSchema.parse(input)).not.toThrow();
  });

  it('should pass with environment using $ref array', () => {
    const input = {
      ...validBase,
      spec: {
        ...validBase.spec,
        environment: [{ $ref: '#/env/ref' }, { $ref: '#/env/ref2' }],
      },
    };
    expect(() => TestSchema.parse(input)).not.toThrow();
  });

  it('should pass with environment.variables', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        environment: {
          variables: [
            {
              kind: 'environment',
              metadata: {
                name: 'DefaultEnv',
                version: '1.0.0',
                namespace: 'default',
              },
              spec: {
                variables: [{ key: 'TOKEN', value: 'abc123', isSecret: true }],
              },
            },
          ],
        },
      },
    };
    expect(() => TestSchema.parse(input)).not.toThrow();
  });

  it('should pass with noauth=true in auth', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        request: [
          {
            method: 'GET',
            resource: '/secure',
            auth: { noauth: true },
          },
        ],
      },
    };
    expect(() => TestSchema.parse(input)).not.toThrow();
  });

  it('should pass with bearerToken in auth', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        request: [
          {
            method: 'GET',
            resource: '/secure',
            auth: { bearerToken: 'my-token' },
          },
        ],
      },
    };
    expect(() => TestSchema.parse(input)).not.toThrow();
  });

  it('should pass with basicAuth in auth', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        request: [
          {
            method: 'POST',
            resource: '/secure',
            auth: {
              basicAuth: {
                username: 'user',
                password: 'pass',
              },
            },
          },
        ],
      },
    };
    expect(() => TestSchema.parse(input)).not.toThrow();
  });

  it('should pass with custom headers', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        request: [
          {
            method: 'GET',
            resource: '/info',
            headers: [
              {
                key: 'Authorization',
                value: 'Bearer xyz',
                description: 'Auth header',
              },
              {
                key: 'X-Custom-Header',
                value: 'value',
              },
            ],
          },
        ],
      },
    };
    expect(() => TestSchema.parse(input)).not.toThrow();
  });

  it('should fail if multiple auth fields are present', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        request: [
          {
            method: 'GET',
            resource: '/secure',
            auth: {
              noauth: true,
              bearerToken: 'token123',
            },
          },
        ],
      },
    };

    expect(() => TestSchema.parse(input)).toThrow(
      'Only one of noauth, bearerToken, or basicAuth must be provided',
    );
  });

  it('should fail if environment has neither $ref nor variables', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        environment: {},
      },
    };
    expect(() => TestSchema.parse(input)).toThrow(
      /Either \$ref or variables.*must be provided/,
    );
  });

  it('should fail if a header is missing key or value', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        request: [
          {
            method: 'GET',
            resource: '/bad',
            headers: [
              {
                key: 'X-Broken',
              } as any,
            ],
          },
        ],
      },
    };
    expect(() => TestSchema.parse(input)).toThrow();
  });

  it('should fail if environment has empty variables array', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        environment: {
          variables: [],
        },
      },
    };
    expect(() => TestSchema.parse(input)).toThrow(
      /Either \$ref or variables.*must be provided/,
    );
  });

  it('should pass with assertions using $ref', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        request: [
          {
            method: 'GET',
            resource: '/status',
            assertions: {
              $ref: '#/assert/ref',
            },
          },
        ],
      },
    };
    expect(() => TestSchema.parse(input)).not.toThrow();
  });

  it('should pass with assertions using array of $ref objects', () => {
    const input = {
      ...validBase,
      spec: {
        ...validBase.spec,
        request: [
          {
            method: 'GET',
            resource: '/status',
            assertions: [{ $ref: '#/assert/ref' }, { $ref: '#/assert/ref2' }],
          },
        ],
      },
    };
    expect(() => TestSchema.parse(input)).not.toThrow();
  });

  it('should pass with assertions using expressions', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        request: [
          {
            method: 'GET',
            resource: '/status',
            assertions: {
              assertions: [
                {
                  kind: 'assertion',
                  metadata: {
                    name: 'CheckStatus',
                    version: '1.0.0',
                    namespace: 'default',
                  },
                  spec: [
                    {
                      name: 'Check status value',
                      key: 'status',
                      value: 200,
                      action: 'equals',
                    },
                  ],
                },
              ],
            },
          },
        ],
      },
    };
    expect(() => TestSchema.parse(input)).not.toThrow();
  });

  it('should fail if payload has multiple formats', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        request: [
          {
            method: 'POST',
            resource: '/submit',
            payload: {
              raw: { json: '{}' },
              formData: [{ key: 'name', value: 'John' }],
            },
          },
        ],
      },
    };
    expect(() => TestSchema.parse(input)).toThrow(
      /Exactly one of raw, urlEncodedFormData, or formData must be provided/,
    );
  });

  it('should pass with a valid raw payload', () => {
    const input = {
      ...validTest,
      spec: {
        ...validTest.spec,
        request: [
          {
            method: 'POST',
            resource: '/submit',
            payload: {
              raw: { json: '{}' },
            },
          },
        ],
      },
    };
    expect(() => TestSchema.parse(input)).not.toThrow();
  });
});
