import { test } from '@japa/runner'
import sinon from 'sinon'
import { Http } from '../../../../src/mocking/legacy/Http.js'
import { ARCProject } from '../../../../src/models/legacy/models/ArcLegacyProject.js'
import type { ARCSavedRequest } from '../../../../src/legacy.js'

test.group('project()', (group) => {
  let http: Http

  group.each.setup(() => {
    http = new Http()
  })

  test('returns an object', ({ assert }) => {
    const result = http.project()
    assert.typeOf(result, 'object')
  })

  test('has the _id', ({ assert }) => {
    const result = http.project()
    assert.typeOf(result._id, 'string')
  })

  test('has the name', ({ assert }) => {
    const result = http.project()
    assert.typeOf(result.name, 'string')
  })

  test('has the order', ({ assert }) => {
    const result = http.project()
    assert.typeOf(result.order, 'number')
  })

  test('has the description', ({ assert }) => {
    const result = http.project()
    assert.typeOf(result.description, 'string')
  })

  test('has the requests', ({ assert }) => {
    const result = http.project()
    assert.typeOf(result.requests, 'array')
    // @ts-expect-error Used in testing
    assert.lengthOf(result.requests, 0)
  })

  test('adds passed requestId', ({ assert }) => {
    const result = http.project({
      requestId: 'test',
    })
    assert.deepEqual(result.requests, ['test'])
  })

  test('auto-generates request id', ({ assert }) => {
    const result = http.project({
      autoRequestId: true,
    })
    // @ts-expect-error Used in testing
    assert.typeOf(result.requests[0], 'string')
  })
})

test.group('description()', (group) => {
  let http: Http

  group.each.setup(() => {
    http = new Http()
  })

  test('returns a string', ({ assert }) => {
    const result = http.description()
    if (result === undefined) {
      // there's 30% chance that generated value is undefined
      return
    }
    assert.typeOf(result, 'string')
  })

  test('always returns undefined for noDescription', ({ assert }) => {
    const result = http.description({
      noDescription: true,
    })
    assert.isUndefined(result)
  })

  test('always returns value for forceDescription', ({ assert }) => {
    const result = http.description({
      forceDescription: true,
    })
    assert.typeOf(result, 'string')
  })
})

test.group('saved()', (group) => {
  let http: Http

  group.each.setup(() => {
    http = new Http()
  })

  test('returns an object', ({ assert }) => {
    const result = http.saved()
    assert.typeOf(result, 'object')
  })

  test('has the {property} property of a type {type}')
    .with([
      { property: '_id', type: 'string' },
      { property: 'method', type: 'string' },
      { property: 'url', type: 'string' },
      { property: 'headers', type: 'string' },
      { property: 'created', type: 'number' },
      { property: 'updated', type: 'number' },
      { property: 'type', type: 'string' },
      { property: 'name', type: 'string' },
      { property: 'midnight', type: 'number' },
    ])
    .run(({ assert }, { property, type }) => {
      const result = http.saved()
      assert.typeOf(result[property as keyof ARCSavedRequest], type)
    })

  test(`has the description property of a type string`, ({ assert }) => {
    const result = http.saved()
    if (result.description === undefined) {
      // there's 30% chance that generated value is undefined
      return
    }
    assert.typeOf(result.description, 'string')
  })

  test('adds a project', ({ assert }) => {
    const project = 'a'
    const result = http.saved({
      project,
    })
    assert.deepEqual(result.projects, [project])
  })

  test('sets the type property', ({ assert }) => {
    const result = http.saved()
    assert.equal(result.type, 'saved')
  })
})

test.group('history()', (group) => {
  let http: Http

  group.each.setup(() => {
    http = new Http()
  })

  test('returns an object', ({ assert }) => {
    const result = http.history()
    assert.typeOf(result, 'object')
  })
  ;[
    ['url', 'string'],
    ['method', 'string'],
    ['headers', 'string'],
    ['created', 'number'],
    ['updated', 'number'],
    ['type', 'string'],
    ['midnight', 'number'],
    ['_id', 'string'],
  ].forEach((item) => {
    test(`has ${item[0]} property of a type ${item[1]}`, ({ assert }) => {
      const result = http.history()
      // @ts-expect-error Used in testing
      assert.typeOf(result[item[0]], item[1])
    })
  })

  test('has the type property', ({ assert }) => {
    const result = http.history()
    assert.equal(result.type, 'history')
  })

  test('has no _id with noId option', ({ assert }) => {
    const result = http.history({
      noId: true,
    })
    assert.isUndefined(result._id)
  })
})

test.group('listHistory()', (group) => {
  let http: Http

  group.each.setup(() => {
    http = new Http()
  })

  test('returns an array', ({ assert }) => {
    const result = http.listHistory()
    assert.typeOf(result, 'array')
  })

  test('has the default number of requests', ({ assert }) => {
    const result = http.listHistory()
    assert.lengthOf(result, 25)
  })

  test('has requested number of requests', ({ assert }) => {
    const result = http.listHistory(5)
    assert.lengthOf(result, 5)
  })

  test('calls history()', ({ assert }) => {
    const spy = sinon.spy(http, 'history')
    http.listHistory(5)
    assert.equal(spy.callCount, 5)
  })
})

test.group('pickProject()', (group) => {
  let http: Http
  let projects: ARCProject[]

  group.each.setup(() => {
    http = new Http()
    projects = http.listProjects(3)
  })

  test('always returns project id', ({ assert }) => {
    const result = http.pickProject({
      projects,
      forceProject: true,
    })
    // @ts-expect-error Used in testing
    assert.notEqual(projects.indexOf(result), -1)
  })

  test('may return project id', ({ assert }) => {
    const result = http.pickProject({
      projects,
    })
    if (result === undefined) {
      return
    }
    assert.notEqual(projects.indexOf(result), -1)
  })

  test('returns undefined when no project', ({ assert }) => {
    const result = http.pickProject()
    assert.isUndefined(result)
  })
})

test.group('listSaved()', (group) => {
  let http: Http

  group.each.setup(() => {
    http = new Http()
  })

  test('returns an array', ({ assert }) => {
    const result = http.listSaved()
    assert.typeOf(result, 'array')
  })

  test('list has default number of requests', ({ assert }) => {
    const result = http.listSaved()
    assert.lengthOf(result, 25)
  })

  test('returns requested number of requests', ({ assert }) => {
    const result = http.listSaved(10)
    assert.lengthOf(result, 10)
  })

  test('calls the pickProject()', ({ assert }) => {
    const spy = sinon.spy(http, 'pickProject')
    http.listSaved(10)
    assert.equal(spy.callCount, 10)
  })

  test('calls saved()', ({ assert }) => {
    const spy = sinon.spy(http, 'saved')
    http.listSaved(10)
    assert.equal(spy.callCount, 10)
  })

  test('adds project to the request', ({ assert }) => {
    const projects: ARCProject[] = [{ _id: '1', name: 'x', description: 'y', order: 1, requests: [] }]
    const result = http.listSaved(2, {
      forceProject: true,
      projects,
    })

    assert.deepEqual(result[0].projects, ['1'])
  })

  test('adds request to the project', ({ assert }) => {
    const projects: ARCProject[] = [{ _id: '1', name: 'x', description: 'y', order: 1, requests: [] }]
    http.listSaved(2, {
      forceProject: true,
      projects,
    })

    assert.typeOf(projects[0].requests![0], 'string')
  })
})

test.group('listProjects()', (group) => {
  let http: Http

  group.each.setup(() => {
    http = new Http()
  })

  test('returns an array', ({ assert }) => {
    const result = http.listProjects()
    assert.typeOf(result, 'array')
  })

  test('has default number of requests', ({ assert }) => {
    const result = http.listProjects()
    assert.lengthOf(result, 5)
  })

  test('has requested number of requests', ({ assert }) => {
    const result = http.listProjects(10)
    assert.lengthOf(result, 10)
  })

  test('calls project()', ({ assert }) => {
    const spy = sinon.spy(http, 'project')
    http.listProjects(10)
    assert.equal(spy.callCount, 10)
  })
})

test.group('savedData()', (group) => {
  let http: Http

  group.each.setup(() => {
    http = new Http()
  })

  test('returns an object', ({ assert }) => {
    const result = http.savedData()
    assert.typeOf(result, 'object')
  })

  test('calls listProjects()', ({ assert }) => {
    const spy = sinon.spy(http, 'listProjects')
    http.savedData()
    assert.equal(spy.callCount, 1)
  })

  test('calls listSaved()', ({ assert }) => {
    const spy = sinon.spy(http, 'listSaved')
    http.savedData()
    assert.equal(spy.callCount, 1)
  })

  test('passes the size argument to the listProjects()', ({ assert }) => {
    const result = http.savedData(2, 1)
    assert.lengthOf(result.projects, 1)
  })

  test('passes the size argument to listSaved()', ({ assert }) => {
    const result = http.savedData(2, 1)
    assert.lengthOf(result.requests, 2)
  })
})

test.group('transportRequest()', (group) => {
  let http: Http

  group.each.setup(() => {
    http = new Http()
  })
  ;[
    ['url', 'string'],
    ['method', 'string'],
    ['startTime', 'number'],
    ['endTime', 'number'],
    ['headers', 'string'],
    // ['payload', 'string'],
    ['httpMessage', 'string'],
  ].forEach(([prop, type]) => {
    test(`has the ${prop} property by default`, ({ assert }) => {
      const result = http.transportRequest()
      // @ts-expect-error Used in testing
      assert.typeOf(result[prop], type)
    })
  })

  test('has no body when requested', ({ assert }) => {
    const result = http.transportRequest({ payload: { noPayload: true } })
    assert.isUndefined(result.payload)
  })

  test('has the body when requested', ({ assert }) => {
    const result = http.transportRequest({ payload: { force: true } })
    assert.typeOf(result.payload, 'string')
  })

  test('has no httpMessage when requested', ({ assert }) => {
    const result = http.transportRequest({ noHttpMessage: true })
    assert.isUndefined(result.httpMessage)
  })
})

test.group('#response.redirectResponse()', (group) => {
  let http: Http

  group.each.setup(() => {
    http = new Http()
  })
  ;[
    ['response', 'object'],
    ['startTime', 'number'],
    ['endTime', 'number'],
  ].forEach(([prop, type]) => {
    test(`has the ${prop} property by default`, ({ assert }) => {
      const result = http.response.redirectResponse()
      // @ts-expect-error Used in testing
      assert.typeOf(result[prop], type)
    })
  })

  test('has no timings property by default', ({ assert }) => {
    const result = http.response.redirectResponse()
    assert.isUndefined(result.timings)
  })

  test('add the timings property', ({ assert }) => {
    const result = http.response.redirectResponse({ timings: true })
    assert.typeOf(result.timings, 'object')
  })
  ;[
    ['status', 'number'],
    ['statusText', 'string'],
    ['headers', 'string'],
  ].forEach(([prop, type]) => {
    test(`has the ${prop} property on the response`, ({ assert }) => {
      const result = http.response.redirectResponse()
      // @ts-expect-error Used in testing
      assert.typeOf(result.response[prop], type)
    })
  })

  test('has the location property on the headers', ({ assert }) => {
    const result = http.response.redirectResponse()
    assert.include(result.response.headers, 'location: ')
  })

  test('has the payload property on the request', ({ assert }) => {
    const result = http.response.redirectResponse({ body: true })
    assert.typeOf(result.response.payload, 'string')
  })

  test('ignores the payload when not in options', ({ assert }) => {
    const result = http.response.redirectResponse()
    assert.isUndefined(result.response.payload, 'string')
  })

  test('has the timings property', ({ assert }) => {
    const result = http.response.redirectResponse({ timings: true })
    assert.typeOf(result.timings, 'object')
  })
})

test.group('#response.arcResponse()', (group) => {
  let http: Http

  group.each.setup(() => {
    http = new Http()
  })
  ;[
    ['status', 'number'],
    ['statusText', 'string'],
    ['headers', 'string'],
    ['payload', 'string'],
    ['loadingTime', 'number'],
    ['size', 'object'],
  ].forEach(([prop, type]) => {
    test(`has the ${prop} property by default`, ({ assert }) => {
      const result = http.response.arcResponse()
      // @ts-expect-error Used in testing
      assert.typeOf(result[prop], type)
    })
  })

  test('ignores the payload when in options', ({ assert }) => {
    const result = http.response.arcResponse({ noBody: true })
    assert.isUndefined(result.payload)
  })

  test('ignores the size when in options', ({ assert }) => {
    const result = http.response.arcResponse({ noSize: true })
    assert.isUndefined(result.size)
  })

  test('has the timings property', ({ assert }) => {
    const result = http.response.arcResponse({ timings: true })
    assert.typeOf(result.timings, 'object')
  })

  test('has the redirects property', ({ assert }) => {
    const result = http.response.arcResponse({ redirects: true })
    assert.typeOf(result.redirects, 'array')
  })

  test('has the specific response group', ({ assert }) => {
    let result = http.response.arcResponse({ statusGroup: 2 })
    assert.isAbove(result.status, 199)
    assert.isBelow(result.status, 300)
    result = http.response.arcResponse({ statusGroup: 3 })
    assert.isAbove(result.status, 299)
    assert.isBelow(result.status, 400)
  })
})

test.group('#response.arcErrorResponse()', (group) => {
  let http: Http

  group.each.setup(() => {
    http = new Http()
  })
  ;[
    ['error', 'error'],
    ['status', 'number'],
  ].forEach(([prop, type]) => {
    test(`has the ${prop} property by default`, ({ assert }) => {
      const result = http.response.arcErrorResponse()
      // @ts-expect-error Used in testing
      assert.typeOf(result[prop], type)
    })
  })
})
