/**
 * @license
 * Copyright 2022 Open Ag Data Alliance
 *
 * Use of this source code is governed by an MIT-style
 * license that can be found in the LICENSE file or at
 * https://opensource.org/licenses/MIT.
 */

import test from 'ava';

import { dirname, isAbsolute, join, relative } from 'node:path';

import { $RefParser } from '@apidevtools/json-schema-ref-parser';
import type { JSONSchema6 } from 'json-schema';
import type { JSONSchema8 as Schema } from 'jsonschema8';

import _Ajv from 'ajv';

import loadAllSchemas from './index.js';

// eslint-disable-next-line @typescript-eslint/naming-convention
const Ajv = _Ajv as unknown as typeof _Ajv.default;

/**
 * @todo where should this live?
 */
export async function loadSchema(uri: string) {
  const r = /^https:\/\/formats\.openag\.io/i;

  if (r.test(uri)) {
    // Use local version of openag schemas
    const file = uri.replace(r, '.').replace(/\.json$/, '');
    return import(file);
  }

  throw new Error(`Unknown schema URI: ${uri}`);
  /*
    // Try to fetch schema online
    const { data: schema } = await axios.get<Schema>(uri);
    return schema;
     */
}

const ajv = new Ajv({
  async loadSchema(uri) {
    return loadSchema(uri);
  },
  /* ProcessCode, */
  inlineRefs: false,
  allErrors: true,
  strict: false,
  // AJV complains about standard formats if this is on
  validateFormats: false,
});
test.before('Initialize JSON Schema validator', async () => {
  const meta = await $RefParser.dereference(
    'https://json-schema.org/draft/2019-09/schema',
  );

  // ???: Why does compileAsync not work for meta schema?
  ajv.addMetaSchema(meta);
});

// TODO: Figure out less hacky way to make it find the files correctly
let checkReferences: (key: string, schema: Schema) => Promise<unknown>;
test.before('Initialize $ref checker', () => {
  checkReferences = async (key: string, schema: Schema) => {
    const $refparser = new $RefParser();
    return $refparser.dereference(schema as JSONSchema6, {
      resolve: {
        file: {
          order: 0,
          canRead: true,
          // TODO: Support external $ref
          async read({ url }: { url: string }) {
            const r = /^https:\/\/formats\.openag\.io/;
            const directory = './';
            const path = url.startsWith('https://formats.openag.io')
              ? url.replace(r, '')
              : relative('', url);
            const file = `./${(isAbsolute(path)
              ? join(directory, path)
              : join(directory, dirname(key), path)
            ).replace(/\.json$/, '.cjs')}`;
            return import(file);
          },
        },
      },
    });
  };
});

// TODO: Can you make these parallel in ava?
for await (const { schema, key } of loadAllSchemas()) {
  test.before(`Compile schema ${key}`, async () => {
    try {
      await ajv.compileAsync(schema);
    } catch {
      // Already compiled?
    }
  });

  test(`${key} should be valid JSON Schema`, (t) => {
    t.assert(ajv.validateSchema(schema));
  });

  // $id needs to be consistent with file structure or most tools get upset
  test(`${key} should have consistent $id`, (t) => {
    const { $id } = schema;
    t.is($id, `https://${join('formats.openag.io/', key)}`);
  });

  test.todo(`${key} should have valid self $ref's`);

  // eslint-disable-next-line @typescript-eslint/no-loop-func
  test(`${key} should have valid external $ref's`, async (t) => {
    await t.notThrowsAsync(checkReferences(key, schema));
  });

  test(`${key} should have valid default`, (t) => {
    // eslint-disable-next-line unicorn/prevent-abbreviations
    const { default: def } = schema;
    t.plan(def ? 1 : 0);
    if (def) {
      // eslint-disable-next-line ava/assertion-arguments
      t.assert(ajv.validate(schema, def), ajv.errorsText());
    }
  });

  test(`${key} should validate examples`, (t) => {
    const { examples = [] } = schema;
    t.plan(examples?.length ?? 0);
    for (const example of examples) {
      // eslint-disable-next-line ava/assertion-arguments
      t.assert(ajv.validate(schema, example), ajv.errorsText());
    }
  });
}