/*
 * Copyright 2021 Juan Cruz Viotti
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import tap from 'tap'

import {
  isDeepStrictEqual
} from 'util'

import {
  readdirSync,
  readFileSync,
  writeFileSync,
  statSync
} from 'fs'

import {
  resolve
} from 'path'

import {
  JSONValue,
  JSONSchema,
  compileSchema,
  serialize,
  deserialize,
  Encoding
} from '../../lib'

import {
  validateSchema,
  EncodingSchema
} from '../../lib/schema'

import {
  preprocessSchema
} from '../../lib/preprocessor'

const TEST_DIRECTORY: string = __dirname
const SRC_TEST_DIRECTORY: string = resolve(__dirname, '..', '..', '..', 'test', 'e2e')

const safeReadFile = (filePath: string): string | null => {
  try {
    return readFileSync(filePath, 'utf8')
  } catch (error) {
    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    if (error.code === 'ENOENT') {
      return null
    }

    throw error
  }
}

type SchemaType = 'schema-driven' | 'schema-less'

const writeResult = (testCase: string, type: SchemaType, name: string, value: Encoding | EncodingSchema): void => {
  const destination: string = resolve(SRC_TEST_DIRECTORY, testCase, type, name)

  const currentContent: string | null = safeReadFile(destination)
  if (typeof currentContent === 'string' &&
    isDeepStrictEqual(JSON.parse(currentContent), value)) {
    return
  }

  const content: string = JSON.stringify(value, null, 2)
  writeFileSync(destination, `${content}\n`, 'utf8')
}

for (const testCase of readdirSync(TEST_DIRECTORY)) {
  const testCasePath: string = resolve(TEST_DIRECTORY, testCase)
  if (!statSync(testCasePath).isDirectory()) {
    continue
  }

  const types: SchemaType[] = [ 'schema-driven', 'schema-less' ]

  for (const type of types) {
    tap.test(`${testCase} (${type})`, async (test) => {
      const schema: JSONSchema = type === 'schema-driven'
        ? JSON.parse(readFileSync(resolve(testCasePath, type, 'schema.json'), 'utf8'))
        : {}
      const value: JSONValue = JSON.parse(readFileSync(resolve(testCasePath, 'document.json'), 'utf8'))

      const encodingSchema: EncodingSchema = await preprocessSchema(schema)
      test.true(validateSchema(encodingSchema, value))

      const encoding: Encoding = await compileSchema(schema)

      // Record the encoding and canonical schemas for debugging purposes
      writeResult(testCase, type, 'encoding.json', encoding)
      writeResult(testCase, type, 'canonical.json', encodingSchema)

      const buffer: Buffer = serialize(encoding, value)
      const result: JSONValue = deserialize(encoding, buffer)

      // Record the buffer for debugging purposes too
      writeFileSync(resolve(SRC_TEST_DIRECTORY, testCase, type, 'output.bin'), buffer)

      // Record the buffer size for debugging purposes
      const size: string = String(buffer.length)
      writeFileSync(resolve(SRC_TEST_DIRECTORY, testCase, type, 'size'), `${size}\n`, 'utf8')

      test.strictSame(value, result)
      test.end()
    })
  }
}
