import { test } from '@japa/runner'
import { SecurityProcessor, IOidcTokenInfo, HttpRequest, IHttpRequest, Property } from '../../../src/index.js'

test.group('applyBasicAuth()', (group) => {
  let request: IHttpRequest
  group.each.setup(() => {
    const obj = new HttpRequest()
    request = obj.toJSON()
  })

  test('adds the authorization header to the request', ({ assert }) => {
    SecurityProcessor.applyBasicAuth(request, {
      username: 'a',
      password: 'b',
    })
    assert.equal(request.headers, 'authorization: Basic YTpi')
  })

  test('does nothing when no username', ({ assert }) => {
    SecurityProcessor.applyBasicAuth(request, {
      password: 'b',
    })
    assert.isUndefined(request.headers)
  })

  test('sets the empty password', ({ assert }) => {
    SecurityProcessor.applyBasicAuth(request, {
      username: 'a',
    })
    assert.equal(request.headers, 'authorization: Basic YTo=')
  })

  test('applies the header to the headers model', ({ assert }) => {
    request.headersModel = []
    SecurityProcessor.applyBasicAuth(request, {
      username: 'a',
      password: 'b',
    })
    assert.lengthOf(request.headersModel!, 1, 'has the header')
    const [prop] = request.headersModel!
    assert.equal(prop.name, 'authorization', 'has the authorization header')
    assert.equal(prop.value, 'Basic YTpi', 'has the header value')
    assert.equal(prop.enabled, true, 'has the header is enabled')
  })

  test('keeps the disabled model items', ({ assert }) => {
    request.headersModel = [Property.String('a', 'b', false).toJSON()]
    SecurityProcessor.applyBasicAuth(request, {
      username: 'a',
      password: 'b',
    })
    assert.lengthOf(request.headersModel!, 2, 'has the header')
  })
})

test.group('applyOAuth2()', (group) => {
  let request: IHttpRequest
  group.each.setup(() => {
    const obj = new HttpRequest()
    obj.url = 'https://api.com'
    request = obj.toJSON()
  })

  test('adds the default header', ({ assert }) => {
    SecurityProcessor.applyOAuth2(request, {
      accessToken: 'test123',
    })
    assert.equal(request.headers, 'authorization: Bearer test123')
  })

  test('respects the tokenType configuration for header delivery', ({ assert }) => {
    SecurityProcessor.applyOAuth2(request, {
      accessToken: 'test123',
      tokenType: 'Other',
    })
    assert.equal(request.headers, 'authorization: Other test123')
  })

  test('respects the deliveryName configuration for header delivery', ({ assert }) => {
    SecurityProcessor.applyOAuth2(request, {
      accessToken: 'test123',
      deliveryName: 'x-auth',
    })
    assert.equal(request.headers, 'x-auth: Bearer test123')
  })

  test('adds the default query parameter', ({ assert }) => {
    SecurityProcessor.applyOAuth2(request, {
      accessToken: 'test123',
      deliveryMethod: 'query',
    })
    assert.equal(request.url, 'https://api.com?authorization=Bearer+test123')
  })

  test('respects the tokenType configuration for query parameter delivery', ({ assert }) => {
    SecurityProcessor.applyOAuth2(request, {
      accessToken: 'test123',
      tokenType: 'Other',
      deliveryMethod: 'query',
    })
    assert.equal(request.url, 'https://api.com?authorization=Other+test123')
  })

  test('respects the deliveryName configuration for query parameter delivery', ({ assert }) => {
    SecurityProcessor.applyOAuth2(request, {
      accessToken: 'test123',
      deliveryName: 'auth',
      deliveryMethod: 'query',
    })
    assert.equal(request.url, 'https://api.com?auth=Bearer+test123')
  })

  test('does nothing when no access token', ({ assert }) => {
    SecurityProcessor.applyOAuth2(request, {
      deliveryName: 'auth',
      deliveryMethod: 'query',
    })
    assert.equal(request.url, 'https://api.com')
  })

  test('applies the header to the headers model', ({ assert }) => {
    request.headersModel = []
    SecurityProcessor.applyOAuth2(request, {
      accessToken: 'test123',
    })
    assert.lengthOf(request.headersModel!, 1, 'has the header')
    const [prop] = request.headersModel!
    assert.equal(prop.name, 'authorization', 'has the authorization header')
    assert.equal(prop.value, 'Bearer test123', 'has the header value')
    assert.equal(prop.enabled, true, 'has the header is enabled')
  })

  test('applies the header to the headers model with delivery name', ({ assert }) => {
    request.headersModel = []
    SecurityProcessor.applyOAuth2(request, {
      accessToken: 'test123',
      deliveryName: 'auth',
    })
    assert.lengthOf(request.headersModel!, 1, 'has the header')
    const [prop] = request.headersModel!
    assert.equal(prop.name, 'auth', 'has the authorization header')
    assert.equal(prop.value, 'Bearer test123', 'has the header value')
    assert.equal(prop.enabled, true, 'has the header is enabled')
  })
})

test.group('applyOpenId()', (group) => {
  let request: IHttpRequest
  group.each.setup(() => {
    const obj = new HttpRequest()
    request = obj.toJSON()
  })

  test('adds the default header', ({ assert }) => {
    SecurityProcessor.applyOpenId(request, {
      accessToken: 'test123',
    })
    assert.equal(request.headers, 'authorization: Bearer test123')
  })

  test('adds the tokenInUse', ({ assert }) => {
    SecurityProcessor.applyOpenId(request, {
      tokenInUse: 0,
      tokens: [
        {
          responseType: 'code',
          state: '123',
          time: 123,
          accessToken: 'test456',
        },
      ] as IOidcTokenInfo[],
    })
    assert.equal(request.headers, 'authorization: Bearer test456')
  })

  test('does nothing when invalid tokenInUse', ({ assert }) => {
    SecurityProcessor.applyOpenId(request, {
      tokenInUse: 2,
      tokens: [
        {
          responseType: 'code',
          state: '123',
          time: 123,
          accessToken: 'test456',
        },
      ] as IOidcTokenInfo[],
    })
    assert.isUndefined(request.headers)
  })

  test('does nothing when no tokens', ({ assert }) => {
    SecurityProcessor.applyOpenId(request, {
      tokenInUse: 2,
    })
    assert.isUndefined(request.headers)
  })
})

test.group('applyBearer()', (group) => {
  let request: IHttpRequest
  group.each.setup(() => {
    const obj = new HttpRequest()
    request = obj.toJSON()
  })

  test('adds the authorization header', ({ assert }) => {
    SecurityProcessor.applyBearer(request, {
      token: '123',
    })
    assert.equal(request.headers, 'authorization: Bearer 123')
  })

  test('does nothing when no token', ({ assert }) => {
    SecurityProcessor.applyBearer(request, {
      token: '',
    })
    assert.isUndefined(request.headers)
  })

  test('applies the header to the headers model', ({ assert }) => {
    request.headersModel = []
    SecurityProcessor.applyBearer(request, {
      token: '123',
    })
    assert.lengthOf(request.headersModel!, 1, 'has the header')
    const [prop] = request.headersModel!
    assert.equal(prop.name, 'authorization', 'has the authorization header')
    assert.equal(prop.value, 'Bearer 123', 'has the header value')
    assert.equal(prop.enabled, true, 'has the header is enabled')
  })
})

test.group('applyCustomAuth()', (group) => {
  let request: IHttpRequest
  group.each.setup(() => {
    const obj = new HttpRequest()
    obj.url = 'https://api.com'
    request = obj.toJSON()
  })

  test('adds query parameters', ({ assert }) => {
    SecurityProcessor.applyCustomAuth(request, {
      query: {
        a: 'b',
        c: 'd',
      },
    })
    assert.equal(request.url, 'https://api.com?a=b&c=d')
  })

  test('replaces existing parameters', ({ assert }) => {
    // should this replace a param or should it append new?
    // because it's a security scheme most likely a replace is better option.
    request.url += '?a=z&c=y'
    SecurityProcessor.applyCustomAuth(request, {
      query: {
        a: 'b',
        c: 'd',
      },
    })
    assert.equal(request.url, 'https://api.com?a=b&c=d')
  })

  test('adds headers to the headers string', ({ assert }) => {
    SecurityProcessor.applyCustomAuth(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.equal(request.headers, 'a: b\nc: d')
  })

  test('adds to existing headers in the headers string', ({ assert }) => {
    request.headers = 'a: z\nc: y'
    SecurityProcessor.applyCustomAuth(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.equal(request.headers, 'a: z,b\nc: y,d')
  })

  test('adds headers to the model', ({ assert }) => {
    request.headersModel = []
    SecurityProcessor.applyCustomAuth(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.lengthOf(request.headersModel!, 2, 'has the header')
    const [p1, p2] = request.headersModel!
    assert.equal(p1.name, 'a', 'has the header #1 name')
    assert.equal(p1.value, 'b', 'has the header #1 value')
    assert.equal(p1.enabled, true, 'the header #1 is enabled')

    assert.equal(p2.name, 'c', 'has the header #1 name')
    assert.equal(p2.value, 'd', 'has the header #1 value')
    assert.equal(p2.enabled, true, 'the header #1 is enabled')
  })

  test('adds to existing headers in the model', ({ assert }) => {
    request.headersModel = [Property.String('a', 'z', true).toJSON()]
    SecurityProcessor.applyCustomAuth(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.lengthOf(request.headersModel!, 2, 'has the header')
    const [p1, p2] = request.headersModel!
    assert.equal(p1.name, 'a', 'has the header #1 name')
    assert.equal(p1.value, 'z,b', 'has the header #1 value')
    assert.equal(p1.enabled, true, 'the header #1 is enabled')

    assert.equal(p2.name, 'c', 'has the header #1 name')
    assert.equal(p2.value, 'd', 'has the header #1 value')
    assert.equal(p2.enabled, true, 'the header #1 is enabled')
  })

  test('keeps the disabled model items', ({ assert }) => {
    request.headersModel = [Property.String('a', 'b', false).toJSON()]
    SecurityProcessor.applyCustomAuth(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.lengthOf(request.headersModel!, 3, 'has the header')
  })
})

test.group('applyApiKeys()', (group) => {
  let request: IHttpRequest
  group.each.setup(() => {
    const obj = new HttpRequest()
    obj.url = 'https://api.com'
    request = obj.toJSON()
  })

  test('adds query parameters', ({ assert }) => {
    SecurityProcessor.applyApiKeys(request, {
      query: {
        a: 'b',
        c: 'd',
      },
    })
    assert.equal(request.url, 'https://api.com?a=b&c=d')
  })

  test('replaces existing parameters', ({ assert }) => {
    // should this replace a param or should it append new?
    // because it's a security scheme most likely a replace is better option.
    request.url += '?a=z&c=y'
    SecurityProcessor.applyApiKeys(request, {
      query: {
        a: 'b',
        c: 'd',
      },
    })
    assert.equal(request.url, 'https://api.com?a=b&c=d')
  })

  test('adds headers to the headers string', ({ assert }) => {
    SecurityProcessor.applyApiKeys(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.equal(request.headers, 'a: b\nc: d')
  })

  test('adds to existing headers in the headers string', ({ assert }) => {
    request.headers = 'a: z\nc: y'
    SecurityProcessor.applyApiKeys(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.equal(request.headers, 'a: z,b\nc: y,d')
  })

  test('adds headers to the model', ({ assert }) => {
    request.headersModel = []
    SecurityProcessor.applyApiKeys(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.lengthOf(request.headersModel!, 2, 'has the header')
    const [p1, p2] = request.headersModel!
    assert.equal(p1.name, 'a', 'has the header #1 name')
    assert.equal(p1.value, 'b', 'has the header #1 value')
    assert.equal(p1.enabled, true, 'the header #1 is enabled')

    assert.equal(p2.name, 'c', 'has the header #1 name')
    assert.equal(p2.value, 'd', 'has the header #1 value')
    assert.equal(p2.enabled, true, 'the header #1 is enabled')
  })

  test('adds to existing headers in the model', ({ assert }) => {
    request.headersModel = [Property.String('a', 'z', true).toJSON()]
    SecurityProcessor.applyApiKeys(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.lengthOf(request.headersModel!, 2, 'has the header')
    const [p1, p2] = request.headersModel!
    assert.equal(p1.name, 'a', 'has the header #1 name')
    assert.equal(p1.value, 'z,b', 'has the header #1 value')
    assert.equal(p1.enabled, true, 'the header #1 is enabled')

    assert.equal(p2.name, 'c', 'has the header #1 name')
    assert.equal(p2.value, 'd', 'has the header #1 value')
    assert.equal(p2.enabled, true, 'the header #1 is enabled')
  })

  test('keeps the disabled model items', ({ assert }) => {
    request.headersModel = [Property.String('a', 'b', false).toJSON()]
    SecurityProcessor.applyApiKeys(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.lengthOf(request.headersModel!, 3, 'has the header')
  })
})

test.group('applyPassThrough()', (group) => {
  let request: IHttpRequest
  group.each.setup(() => {
    const obj = new HttpRequest()
    obj.url = 'https://api.com'
    request = obj.toJSON()
  })

  test('adds query parameters', ({ assert }) => {
    SecurityProcessor.applyPassThrough(request, {
      query: {
        a: 'b',
        c: 'd',
      },
    })
    assert.equal(request.url, 'https://api.com?a=b&c=d')
  })

  test('replaces existing parameters', ({ assert }) => {
    // should this replace a param or should it append new?
    // because it's a security scheme most likely a replace is better option.
    request.url += '?a=z&c=y'
    SecurityProcessor.applyPassThrough(request, {
      query: {
        a: 'b',
        c: 'd',
      },
    })
    assert.equal(request.url, 'https://api.com?a=b&c=d')
  })

  test('adds headers to the headers string', ({ assert }) => {
    SecurityProcessor.applyPassThrough(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.equal(request.headers, 'a: b\nc: d')
  })

  test('adds to existing headers in the headers string', ({ assert }) => {
    request.headers = 'a: z\nc: y'
    SecurityProcessor.applyPassThrough(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.equal(request.headers, 'a: z,b\nc: y,d')
  })

  test('adds headers to the model', ({ assert }) => {
    request.headersModel = []
    SecurityProcessor.applyPassThrough(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.lengthOf(request.headersModel!, 2, 'has the header')
    const [p1, p2] = request.headersModel!
    assert.equal(p1.name, 'a', 'has the header #1 name')
    assert.equal(p1.value, 'b', 'has the header #1 value')
    assert.equal(p1.enabled, true, 'the header #1 is enabled')

    assert.equal(p2.name, 'c', 'has the header #1 name')
    assert.equal(p2.value, 'd', 'has the header #1 value')
    assert.equal(p2.enabled, true, 'the header #1 is enabled')
  })

  test('adds to existing headers in the model', ({ assert }) => {
    request.headersModel = [Property.String('a', 'z', true).toJSON()]
    SecurityProcessor.applyPassThrough(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.lengthOf(request.headersModel!, 2, 'has the header')
    const [p1, p2] = request.headersModel!
    assert.equal(p1.name, 'a', 'has the header #1 name')
    assert.equal(p1.value, 'z,b', 'has the header #1 value')
    assert.equal(p1.enabled, true, 'the header #1 is enabled')

    assert.equal(p2.name, 'c', 'has the header #1 name')
    assert.equal(p2.value, 'd', 'has the header #1 value')
    assert.equal(p2.enabled, true, 'the header #1 is enabled')
  })

  test('keeps the disabled model items', ({ assert }) => {
    request.headersModel = [Property.String('a', 'b', false).toJSON()]
    SecurityProcessor.applyPassThrough(request, {
      header: {
        a: 'b',
        c: 'd',
      },
    })
    assert.lengthOf(request.headersModel!, 3, 'has the header')
  })
})
