'use strict'

const fs = require('node:fs')
const path = require('node:path')
const t = require('tap')
const fastify = require('fastify')
const sanitize = require('sanitize-filename')

const { StandaloneValidator: AjvStandaloneValidator } = require('../')

function generateFileName (routeOpts) {
  return `/ajv-generated-${sanitize(routeOpts.schema.$id)}-${routeOpts.method}-${routeOpts.httpPart}-${sanitize(routeOpts.url)}.js`
}

const generatedFileNames = []

t.test('standalone', t => {
  t.plan(4)

  t.teardown(async () => {
    for (const fileName of generatedFileNames) {
      await fs.promises.unlink(path.join(__dirname, fileName))
    }
  })

  t.test('errors', t => {
    t.plan(2)
    t.throws(() => {
      AjvStandaloneValidator()
    }, 'missing restoreFunction')
    t.throws(() => {
      AjvStandaloneValidator({ readMode: false })
    }, 'missing storeFunction')
  })

  t.test('generate standalone code', t => {
    t.plan(5)

    const base = {
      $id: 'urn:schema:base',
      definitions: {
        hello: { type: 'string' }
      },
      type: 'object',
      properties: {
        hello: { $ref: '#/definitions/hello' }
      }
    }

    const refSchema = {
      $id: 'urn:schema:ref',
      type: 'object',
      properties: {
        hello: { $ref: 'urn:schema:base#/definitions/hello' }
      }
    }

    const endpointSchema = {
      schema: {
        $id: 'urn:schema:endpoint',
        $ref: 'urn:schema:ref'
      }
    }

    const schemaMap = {
      [base.$id]: base,
      [refSchema.$id]: refSchema
    }

    const factory = AjvStandaloneValidator({
      readMode: false,
      storeFunction (routeOpts, schemaValidationCode) {
        t.same(routeOpts, endpointSchema)
        t.type(schemaValidationCode, 'string')
        fs.writeFileSync(path.join(__dirname, '/ajv-generated.js'), schemaValidationCode)
        generatedFileNames.push('/ajv-generated.js')
        t.pass('stored the validation function')
      }
    })

    const compiler = factory(schemaMap)
    compiler(endpointSchema)
    t.pass('compiled the endpoint schema')

    t.test('usage standalone code', t => {
      t.plan(3)
      const standaloneValidate = require('./ajv-generated')

      const valid = standaloneValidate({ hello: 'world' })
      t.ok(valid)

      const invalid = standaloneValidate({ hello: [] })
      t.notOk(invalid)

      t.ok(standaloneValidate)
    })
  })

  t.test('fastify integration - writeMode', async t => {
    t.plan(6)

    const factory = AjvStandaloneValidator({
      readMode: false,
      storeFunction (routeOpts, schemaValidationCode) {
        const fileName = generateFileName(routeOpts)
        t.ok(routeOpts)
        fs.writeFileSync(path.join(__dirname, fileName), schemaValidationCode)
        t.pass('stored the validation function')
        generatedFileNames.push(fileName)
      },
      restoreFunction () {
        t.fail('write mode ON')
      }
    })

    const app = buildApp(factory)
    await app.ready()
  })

  t.test('fastify integration - readMode', async t => {
    t.plan(6)

    const factory = AjvStandaloneValidator({
      readMode: true,
      storeFunction () {
        t.fail('read mode ON')
      },
      restoreFunction (routeOpts) {
        t.pass('restore the validation function')
        const fileName = generateFileName(routeOpts)
        return require(path.join(__dirname, fileName))
      }
    })

    const app = buildApp(factory)
    await app.ready()

    let res = await app.inject({
      url: '/foo',
      method: 'POST',
      payload: { hello: [] }
    })
    t.equal(res.statusCode, 400)

    res = await app.inject({
      url: '/bar?lang=invalid',
      method: 'GET'
    })
    t.equal(res.statusCode, 400)

    res = await app.inject({
      url: '/bar?lang=it',
      method: 'GET'
    })
    t.equal(res.statusCode, 200)
  })

  function buildApp (factory) {
    const app = fastify({
      jsonShorthand: false,
      schemaController: {
        compilersFactory: {
          buildValidator: factory
        }
      }
    })

    app.addSchema({
      $id: 'urn:schema:foo',
      type: 'object',
      properties: {
        name: { type: 'string' },
        id: { type: 'integer' }
      }
    })

    app.post('/foo', {
      schema: {
        body: {
          $id: 'urn:schema:body',
          type: 'object',
          properties: {
            hello: { $ref: 'urn:schema:foo#/properties/name' }
          }
        }
      }
    }, () => { return 'ok' })

    app.get('/bar', {
      schema: {
        query: {
          $id: 'urn:schema:query',
          type: 'object',
          properties: {
            lang: { type: 'string', enum: ['it', 'en'] }
          }
        }
      }
    }, () => { return 'ok' })

    return app
  }
})
