import { test } from '@japa/runner'
import sinon from 'sinon'
import { nanoid } from '../../../../src/nanoid.js'
import {
  HttpProject,
  HttpProjectKind,
  IHttpProjectStoreProxyInit,
  IHttpProjectProxyInit,
  ProxyService,
  Exception,
} from '../../../../src/index.js'
import { HttpNode } from '../../../../src/sdk/HttpNode.js'

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

  group.setup(async () => {
    sandbox = sinon.createSandbox()
  })

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

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

    // Reset the sandbox before each test
    sandbox.restore()
    sandbox = sinon.createSandbox()
  })

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

  test('returns an error when no options', async ({ assert }) => {
    // @ts-expect-error Used in testing
    const message: IHttpProjectStoreProxyInit = {
      kind: HttpProjectKind,
      pid: project.key,
      organization: nanoid(),
    }
    const service = new ProxyService()
    let error: Exception
    try {
      await service.proxyHttpProject(message, token, storeUri)
      error = new Exception('Not thrown', { status: 0 })
    } catch (e) {
      error = e as Exception
    }
    assert.equal(error.status, 400, 'has the error code')
    assert.equal(error.message, 'Invalid request', 'has the message')
    assert.equal(error.help, 'The "options" parameter is required.', 'has the detail')
  })

  test('returns an error when no token', async ({ assert }) => {
    const message: IHttpProjectStoreProxyInit = {
      kind: HttpProjectKind,
      pid: project.key,
      options: {},
      organization: nanoid(),
    }
    const service = new ProxyService()
    let error: Exception
    try {
      await service.proxyHttpProject(message, '', storeUri)
      error = new Exception('Not thrown', { status: 0 })
    } catch (e) {
      error = e as Exception
    }
    assert.equal(error.message, 'Invalid request', 'has the message')
    assert.equal(error.help, 'Set the authentication credentials.', 'has the detail')
  })

  test('returns an error when no store URI', async ({ assert }) => {
    const message: IHttpProjectStoreProxyInit = {
      kind: HttpProjectKind,
      pid: project.key,
      options: {},
      organization: nanoid(),
    }
    const service = new ProxyService()
    let error: Exception
    try {
      await service.proxyHttpProject(message, token, '')
      error = new Exception('Not thrown', { status: 0 })
    } catch (e) {
      error = e as Exception
    }
    assert.equal(error.status, 400, 'has the error code')
    assert.equal(error.message, 'Invalid request', 'has the message')
    assert.equal(error.help, 'The store uri is missing.', 'has the detail')
  })

  test('returns an error when invalid store URI', async ({ assert }) => {
    const message: IHttpProjectStoreProxyInit = {
      kind: HttpProjectKind,
      pid: project.key,
      options: {},
      organization: nanoid(),
    }
    const service = new ProxyService()
    let error: Exception
    try {
      await service.proxyHttpProject(message, token, 'test')
      error = new Exception('Not thrown', { status: 0 })
    } catch (e) {
      error = e as Exception
    }
    assert.equal(error.status, 400, 'has the error code')
    assert.equal(error.message, 'Invalid request', 'has the message')
    assert.equal(error.help, 'The store uri is invalid.', 'has the detail')
  })

  test('returns an error when no project', async ({ assert }) => {
    const message: IHttpProjectStoreProxyInit = {
      kind: HttpProjectKind,
      pid: project.key,
      options: {},
      organization: nanoid(),
    }

    // Mock the HttpNode.get method to return a 404 error
    const httpNodeStub = sandbox.stub(HttpNode.prototype, 'get').rejects(new Exception('Not found', { status: 404 }))

    const service = new ProxyService()
    let error: Exception
    try {
      await service.proxyHttpProject(message, token, storeUri)
      error = new Exception('Not thrown', { status: 0 })
    } catch (e) {
      error = e as Exception
    }
    assert.equal(error.status, 400, 'has the error code')
    assert.equal(error.message, 'Not found', 'has the message')
    assert.isTrue(httpNodeStub.calledOnce, 'HTTP request was made')
  })

  test('proxies the project requests', async ({ assert }) => {
    const message: IHttpProjectStoreProxyInit = {
      kind: HttpProjectKind,
      pid: project.key,
      options: {},
      organization: nanoid(),
    }

    // Mock the HttpNode.get method to return a successful response
    const mockHeaders = new (await import('../../../../src/lib/headers/Headers.js')).Headers()
    mockHeaders.set('x-version', '1')
    const httpNodeStub = sandbox.stub(HttpNode.prototype, 'get').resolves({
      status: 200,
      headers: mockHeaders,
      body: JSON.stringify(project.toJSON()),
    })

    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')
    assert.isTrue(httpNodeStub.calledOnce, 'HTTP request was made')
  })
})

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: Exception
    try {
      await service.proxyHttpProject(message)
      error = new Exception('Not thrown', { status: 0 })
    } catch (e) {
      error = e as Exception
    }
    assert.equal(error.status, 400, 'has the error code')
    assert.equal(error.message, 'Invalid request', 'has the message')
    assert.equal(error.help, '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: Exception
    try {
      await service.proxyHttpProject(message)
      error = new Exception('Not thrown', { status: 0 })
    } catch (e) {
      error = e as Exception
    }
    assert.equal(error.status, 400, 'has the error code')
    assert.equal(error.message, 'Invalid request', 'has the message')
    assert.equal(error.help, 'The "project" parameter is required.', 'has the detail')
  })
})
