import nock from 'nock'
import { test } from '@japa/runner'
import { randomUUID } from 'node:crypto'
import {
  HttpProject,
  HttpProjectKind,
  IHttpProjectStoreProxyInit,
  IHttpProjectProxyInit,
  ProxyService,
  ApiError,
  RouteBuilder,
} from '../../../../src/index.js'

test.group('net-store project', (group) => {
  let project: HttpProject
  const token = 'token-test'
  const storeHostname = 'http://localhost:1234'
  const storeUri = `${storeHostname}/`

  group.setup(async () => {
    if (!nock.isActive()) {
      nock.activate()
    }
  })

  group.teardown(async () => {
    nock.restore()
  })

  group.each.setup(({ context }) => {
    project = HttpProject.fromName('p1')
    project.addRequest(`http://localhost:${context.httpConfig.httpPort}/v1/get`)
    nock.cleanAll()
  })

  test('returns an error when no pid', async ({ assert }) => {
    const message: IHttpProjectStoreProxyInit = {
      kind: HttpProjectKind,
      organization: randomUUID(),
      pid: '',
      options: {},
    }
    const path = RouteBuilder.fileMedia(message.organization, project.key)
    const scope = nock(storeHostname).get(path).reply(200, project.toJSON())
    const service = new ProxyService()
    let error: ApiError
    try {
      await service.proxyHttpProject(message, token, storeUri)
      error = new ApiError('Not thrown', 0)
    } catch (e) {
      error = e as ApiError
    }
    assert.equal(error.code, 400, 'has the error code')
    assert.equal(error.message, 'Invalid request', 'has the message')
    assert.equal(error.detail, 'The "pid" parameter is required.', 'has the detail')
    assert.isFalse(scope.isDone(), 'did not call the store')
  })

  test('returns an error when no options', async ({ assert }) => {
    // @ts-expect-error Used in testing
    const message: IHttpProjectStoreProxyInit = {
      kind: HttpProjectKind,
      pid: project.key,
      organization: randomUUID(),
    }
    const path = RouteBuilder.fileMedia(message.organization, project.key)
    const scope = nock(storeHostname).get(path).reply(200, project.toJSON())
    const service = new ProxyService()
    let error: ApiError
    try {
      await service.proxyHttpProject(message, token, storeUri)
      error = new ApiError('Not thrown', 0)
    } catch (e) {
      error = e as ApiError
    }
    assert.equal(error.code, 400, 'has the error code')
    assert.equal(error.message, 'Invalid request', 'has the message')
    assert.equal(error.detail, 'The "options" parameter is required.', 'has the detail')
    assert.isFalse(scope.isDone(), 'did not call the store')
  })

  test('returns an error when no token', async ({ assert }) => {
    const message: IHttpProjectStoreProxyInit = {
      kind: HttpProjectKind,
      pid: project.key,
      options: {},
      organization: randomUUID(),
    }
    const path = RouteBuilder.fileMedia(message.organization, project.key)
    const scope = nock(storeHostname).get(path).reply(200, project.toJSON())
    const service = new ProxyService()
    let error: ApiError
    try {
      await service.proxyHttpProject(message, '', storeUri)
      error = new ApiError('Not thrown', 0)
    } catch (e) {
      error = e as ApiError
    }
    assert.equal(error.message, 'Invalid request', 'has the message')
    assert.equal(error.detail, 'Set the authentication credentials.', 'has the detail')
    assert.isFalse(scope.isDone(), 'did not call the store')
  })

  test('returns an error when no store URI', async ({ assert }) => {
    const message: IHttpProjectStoreProxyInit = {
      kind: HttpProjectKind,
      pid: project.key,
      options: {},
      organization: randomUUID(),
    }
    const path = RouteBuilder.fileMedia(message.organization, project.key)
    const scope = nock(storeHostname).get(path).reply(200, project.toJSON())
    const service = new ProxyService()
    let error: ApiError
    try {
      await service.proxyHttpProject(message, token, '')
      error = new ApiError('Not thrown', 0)
    } catch (e) {
      error = e as ApiError
    }
    assert.equal(error.code, 400, 'has the error code')
    assert.equal(error.message, 'Invalid request', 'has the message')
    assert.equal(error.detail, 'The store uri is missing.', 'has the detail')
    assert.isFalse(scope.isDone(), 'did not call the store')
  })

  test('returns an error when invalid store URI', async ({ assert }) => {
    const message: IHttpProjectStoreProxyInit = {
      kind: HttpProjectKind,
      pid: project.key,
      options: {},
      organization: randomUUID(),
    }
    const path = RouteBuilder.fileMedia(message.organization, project.key)
    const scope = nock(storeHostname).get(path).reply(200, project.toJSON())
    const service = new ProxyService()
    let error: ApiError
    try {
      await service.proxyHttpProject(message, token, 'test')
      error = new ApiError('Not thrown', 0)
    } catch (e) {
      error = e as ApiError
    }
    assert.equal(error.code, 400, 'has the error code')
    assert.equal(error.message, 'Invalid request', 'has the message')
    assert.equal(error.detail, 'The store uri is invalid.', 'has the detail')
    assert.isFalse(scope.isDone(), 'did not call the store')
  })

  test('returns an error when no project', async ({ assert }) => {
    const message: IHttpProjectStoreProxyInit = {
      kind: HttpProjectKind,
      pid: project.key,
      options: {},
      organization: randomUUID(),
    }
    const path = RouteBuilder.fileMedia(message.organization, project.key)
    nock(storeHostname).get(path).reply(404, new ApiError('Not found', 404).toJSON())
    const service = new ProxyService()
    let error: ApiError
    try {
      await service.proxyHttpProject(message, token, storeUri)
      error = new ApiError('Not thrown', 0)
    } catch (e) {
      error = e as ApiError
    }
    assert.equal(error.code, 400, 'has the error code')
    assert.equal(error.message, 'Not found', 'has the message')
  })

  test('proxies the project requests', async ({ assert }) => {
    const message: IHttpProjectStoreProxyInit = {
      kind: HttpProjectKind,
      pid: project.key,
      options: {},
      organization: randomUUID(),
    }
    const path = RouteBuilder.fileMedia(message.organization, project.key)
    nock(storeHostname).get(path).reply(200, project.toJSON(), { 'x-version': '1' })
    const service = new ProxyService()
    const response = await service.proxyHttpProject(message, token, storeUri)
    const exe = response.result
    assert.typeOf(exe.started, 'number', 'has the response.started')
    assert.typeOf(exe.ended, 'number', 'has the response.ended')
    assert.typeOf(exe.iterations, 'array', 'has the response.iterations')
  })
})

test.group('local project', (group) => {
  let project: HttpProject

  group.each.setup(({ context }) => {
    project = HttpProject.fromName('p1')
    project.addRequest(`http://localhost:${context.httpConfig.httpPort}/v1/get`)
  })

  test('proxies the project requests', async ({ assert }) => {
    const message: IHttpProjectProxyInit = {
      kind: HttpProjectKind,
      project: project.toJSON(),
      options: {},
    }
    const service = new ProxyService()
    const response = await service.proxyHttpProject(message)
    const exe = response.result
    assert.typeOf(exe.started, 'number', 'has the response.started')
    assert.typeOf(exe.ended, 'number', 'has the response.ended')
    assert.typeOf(exe.iterations, 'array', 'has the response.iterations')
  })

  test('returns an error when no options', async ({ assert }) => {
    const message: IHttpProjectProxyInit = {
      kind: HttpProjectKind,
      project: project.toJSON(),
    } as IHttpProjectProxyInit
    const service = new ProxyService()
    let error: ApiError
    try {
      await service.proxyHttpProject(message)
      error = new ApiError('Not thrown', 0)
    } catch (e) {
      error = e as ApiError
    }
    assert.equal(error.code, 400, 'has the error code')
    assert.equal(error.message, 'Invalid request', 'has the message')
    assert.equal(error.detail, 'The "options" parameter is required.', 'has the detail')
  })

  test('returns an error when no project', async ({ assert }) => {
    const message: IHttpProjectProxyInit = {
      kind: HttpProjectKind,
      options: {},
    } as IHttpProjectProxyInit
    const service = new ProxyService()
    let error: ApiError
    try {
      await service.proxyHttpProject(message)
      error = new ApiError('Not thrown', 0)
    } catch (e) {
      error = e as ApiError
    }
    assert.equal(error.code, 400, 'has the error code')
    assert.equal(error.message, 'Invalid request', 'has the message')
    assert.equal(error.detail, 'The "project" parameter is required.', 'has the detail')
  })
})
