import { test } from '@japa/runner'
import { HttpCookie, IHttpCookie } from '../../../src/models/HttpCookie.js'
import { ARCCookie as LegacyARCCookie } from '../../../src/models/legacy/models/Cookies.js'

test.group('HttpCookie.fromLegacy()', (group) => {
  let schema: LegacyARCCookie

  group.each.setup(() => {
    schema = {
      name: 'a',
      value: 'b',
      domain: 'c',
      path: 'd',
    }
  })

  test('sets the name', ({ assert }) => {
    const result = HttpCookie.fromLegacy(schema)
    assert.equal(result.name, schema.name)
  })

  test('sets the value', ({ assert }) => {
    const result = HttpCookie.fromLegacy(schema)
    assert.equal(result.value, schema.value)
  })

  test('sets the value when missing', ({ assert }) => {
    delete schema.value
    const result = HttpCookie.fromLegacy(schema)
    assert.equal(result.value, '')
  })

  test('sets the domain', ({ assert }) => {
    const result = HttpCookie.fromLegacy(schema)
    assert.equal(result.domain, schema.domain)
  })

  test('does not set the domain when missing', ({ assert }) => {
    // @ts-expect-error Used in testing
    delete schema.domain
    const result = HttpCookie.fromLegacy(schema)
    assert.isUndefined(result.domain)
  })

  test('sets the path', ({ assert }) => {
    const result = HttpCookie.fromLegacy(schema)
    assert.equal(result.path, schema.path)
  })

  test('does not set the path when missing', ({ assert }) => {
    // @ts-expect-error Used in testing
    delete schema.path
    const result = HttpCookie.fromLegacy(schema)
    assert.isUndefined(result.path)
  })

  test('sets the expirationDate', ({ assert }) => {
    schema.expires = 123456
    const result = HttpCookie.fromLegacy(schema)
    assert.equal(result.expirationDate, schema.expires)
  })

  test('does not set the expirationDate when missing', ({ assert }) => {
    const result = HttpCookie.fromLegacy(schema)
    assert.isUndefined(result.expirationDate)
  })

  test('sets the hostOnly', ({ assert }) => {
    schema.hostOnly = false
    const result = HttpCookie.fromLegacy(schema)
    assert.isFalse(result.hostOnly)
  })

  test('does not set the hostOnly when missing', ({ assert }) => {
    const result = HttpCookie.fromLegacy(schema)
    assert.isUndefined(result.hostOnly)
  })

  test('sets the httpOnly', ({ assert }) => {
    schema.httpOnly = false
    const result = HttpCookie.fromLegacy(schema)
    assert.isFalse(result.httpOnly)
  })

  test('does not set the httpOnly when missing', ({ assert }) => {
    const result = HttpCookie.fromLegacy(schema)
    assert.isUndefined(result.httpOnly)
  })

  test('sets the secure', ({ assert }) => {
    schema.secure = false
    const result = HttpCookie.fromLegacy(schema)
    assert.isFalse(result.secure)
  })

  test('does not set the secure when missing', ({ assert }) => {
    const result = HttpCookie.fromLegacy(schema)
    assert.isUndefined(result.secure)
  })

  test('sets the session', ({ assert }) => {
    schema.session = true
    const result = HttpCookie.fromLegacy(schema)
    assert.isTrue(result.session)
  })

  test('set the session when not expires', ({ assert }) => {
    const result = HttpCookie.fromLegacy(schema)
    assert.isTrue(result.session)
  })

  test('set the session when expires', ({ assert }) => {
    const result = HttpCookie.fromLegacy({ ...schema, expires: 1234 })
    assert.isFalse(result.session)
  })
})

test.group('HttpCookie constructor()', (group) => {
  const invalid = `test${String.fromCharCode(0x1f)}`
  let schema: IHttpCookie

  group.each.setup(() => {
    schema = {
      name: 'a',
      value: 'b',
      sameSite: 'strict',
    }
  })

  test('sets the name', ({ assert }) => {
    const result = new HttpCookie(schema)
    assert.equal(result.name, schema.name)
  })

  test('sets the default name when missing', ({ assert }) => {
    // @ts-expect-error Used in testing
    delete schema.name
    const result = new HttpCookie(schema)
    assert.equal(result.name, '')
  })

  test('sets the value', ({ assert }) => {
    const result = new HttpCookie(schema)
    assert.equal(result.value, schema.value)
  })

  test('sets the default value when missing', ({ assert }) => {
    // @ts-expect-error Used in testing
    delete schema.value
    const result = new HttpCookie(schema)
    assert.equal(result.value, '')
  })

  test('sets the domain', ({ assert }) => {
    schema.domain = 'dot.com'
    const result = new HttpCookie(schema)
    assert.equal(result.domain, schema.domain)
  })

  test('does not set the domain when missing', ({ assert }) => {
    const result = new HttpCookie(schema)
    assert.isUndefined(result.domain)
  })

  test('sets the path', ({ assert }) => {
    schema.domain = '/abc'
    const result = new HttpCookie(schema)
    assert.equal(result.path, schema.path)
  })

  test('does not set the path when missing', ({ assert }) => {
    const result = new HttpCookie(schema)
    assert.isUndefined(result.path)
  })

  test('sets the expirationDate', ({ assert }) => {
    schema.expirationDate = 123456
    const result = new HttpCookie(schema)
    assert.equal(result.expirationDate, schema.expirationDate)
  })

  test('does not set the expirationDate when missing', ({ assert }) => {
    const result = new HttpCookie(schema)
    assert.isUndefined(result.expirationDate)
  })

  test('sets the expirationDate from string number', ({ assert }) => {
    const cookie = new HttpCookie()
    cookie.expirationDate = '123456'
    assert.equal(cookie.expirationDate, 123456)
  })

  test('sets the hostOnly', ({ assert }) => {
    schema.hostOnly = false
    const result = new HttpCookie(schema)
    assert.isFalse(result.hostOnly)
  })

  test('does not set the hostOnly when missing', ({ assert }) => {
    const result = new HttpCookie(schema)
    assert.isUndefined(result.hostOnly)
  })

  test('sets the httpOnly', ({ assert }) => {
    schema.httpOnly = false
    const result = new HttpCookie(schema)
    assert.isFalse(result.httpOnly)
  })

  test('does not set the httpOnly when missing', ({ assert }) => {
    const result = new HttpCookie(schema)
    assert.isUndefined(result.httpOnly)
  })

  test('sets the secure', ({ assert }) => {
    schema.secure = false
    const result = new HttpCookie(schema)
    assert.isFalse(result.secure)
  })

  test('does not set the secure when missing', ({ assert }) => {
    const result = new HttpCookie(schema)
    assert.isUndefined(result.secure)
  })

  test('sets the session', ({ assert }) => {
    schema.session = true
    const result = new HttpCookie(schema)
    assert.isTrue(result.session)
  })

  test('sets the default session', ({ assert }) => {
    const result = new HttpCookie(schema)
    assert.isTrue(result.session)
  })

  test('creates the default values when no argument', ({ assert }) => {
    const result = new HttpCookie()
    assert.equal(result.name, '')
    assert.equal(result.value, '')
    assert.equal(result.sameSite, 'unspecified')
    assert.isUndefined(result.domain)
    assert.isUndefined(result.path)
    assert.isUndefined(result.expirationDate)
    assert.isUndefined(result.hostOnly)
    assert.isUndefined(result.httpOnly)
    assert.isUndefined(result.secure)
    assert.isTrue(result.session)
  })

  test('creates the cookie from the schema', ({ assert }) => {
    const result = new HttpCookie(JSON.stringify(schema))
    assert.equal(result.name, schema.name)
    assert.equal(result.value, schema.value)
    assert.equal(result.sameSite, schema.sameSite)
    assert.isUndefined(result.domain)
    assert.isUndefined(result.path)
    assert.isUndefined(result.expirationDate)
    assert.isUndefined(result.hostOnly)
    assert.isUndefined(result.httpOnly)
    assert.isUndefined(result.secure)
    assert.isTrue(result.session)
  })

  test('throws an error for invalid name', ({ assert }) => {
    assert.throws(() => {
      new HttpCookie({ name: invalid, value: '', sameSite: 'lax' })
    })
  })

  test('throws an error for invalid value', ({ assert }) => {
    assert.throws(() => {
      new HttpCookie({ name: 'test', value: invalid, sameSite: 'lax' })
    })
  })

  test('throws an error for invalid path', ({ assert }) => {
    assert.throws(() => {
      new HttpCookie({ name: 'test', value: '', sameSite: 'lax', path: invalid })
    })
  })

  test('throws an error for invalid domain', ({ assert }) => {
    assert.throws(() => {
      new HttpCookie({ name: 'test', value: '', sameSite: 'lax', domain: invalid })
    })
  })
})

test.group('HttpCookie #maxAge', () => {
  test('sets the expirationDate', ({ assert }) => {
    const instance = new HttpCookie()
    instance.maxAge = 100
    // @ts-expect-error Used in testing
    assert.approximately(instance.expirationDate, Date.now() + 100000, 1000)
  })

  test('sets the cookie persistent', ({ assert }) => {
    const instance = new HttpCookie()
    instance.maxAge = 100
    assert.isFalse(instance.session)
  })

  test('sets lowest possible expiration date', ({ assert }) => {
    const instance = new HttpCookie()
    instance.maxAge = -100
    const compare = new Date(-8640000000000000).getTime()
    assert.equal(instance.expirationDate, compare)
  })

  test('sets the expirationDate from the "max-age" property', ({ assert }) => {
    const instance = new HttpCookie()
    instance['max-age'] = 100
    // @ts-expect-error Used in testing
    assert.approximately(instance.expirationDate, Date.now() + 100000, 1000)
  })

  test('ignores non-numeric values', ({ assert }) => {
    const instance = new HttpCookie()
    // @ts-expect-error Used in testing
    instance.maxAge = 'test'
    assert.isUndefined(instance.expirationDate)
  })

  test('sets the expirationDate for maxAge 0', ({ assert }) => {
    const instance = new HttpCookie()
    instance.maxAge = 0
    // @ts-expect-error Used in testing
    assert.isBelow(instance.expirationDate, -1)
  })

  test('sets the expirationDate for maxAge -1', ({ assert }) => {
    const instance = new HttpCookie()
    instance.maxAge = -1
    // @ts-expect-error Used in testing
    assert.isBelow(instance.expirationDate, -1)
  })

  test('sets the expirationDate for maxAge 1', ({ assert }) => {
    const instance = new HttpCookie()
    instance.maxAge = 1
    // @ts-expect-error Used in testing
    assert.isAbove(instance.expirationDate, Date.now())
  })
})

test.group('HttpCookie #expires', () => {
  test('sets "expirationDate" from timestamp', ({ assert }) => {
    const instance = new HttpCookie()
    const now = Date.now()
    instance.expires = now
    assert.equal(instance.expirationDate, now)
  })

  test('sets expires from ISO string', ({ assert }) => {
    const instance = new HttpCookie()
    const now = new Date()
    const time = now.getTime()
    instance.expires = now.toISOString()
    assert.equal(instance.expirationDate, time)
  })
})

test.group('HttpCookie toJSON()', (group) => {
  let schema: IHttpCookie
  let instance: HttpCookie

  group.each.setup(() => {
    schema = {
      name: 'a',
      value: 'b',
      sameSite: 'strict',
    }
    instance = new HttpCookie(schema)
  })

  test('serializes the name', ({ assert }) => {
    const result = instance.toJSON()
    assert.equal(result.name, schema.name)
  })

  test('serializes the value', ({ assert }) => {
    const result = instance.toJSON()
    assert.equal(result.value, schema.value)
  })

  test('serializes the sameSite', ({ assert }) => {
    const result = instance.toJSON()
    assert.equal(result.sameSite, schema.sameSite)
  })

  test('serializes the domain', ({ assert }) => {
    instance.domain = 'dot.com'
    const result = instance.toJSON()
    assert.equal(result.domain, instance.domain)
  })

  test('serializes the expirationDate', ({ assert }) => {
    instance.expirationDate = 1234
    const result = instance.toJSON()
    assert.equal(result.expirationDate, instance.expirationDate)
  })

  test('serializes the hostOnly', ({ assert }) => {
    instance.hostOnly = false
    const result = instance.toJSON()
    assert.equal(result.hostOnly, instance.hostOnly)
  })

  test('serializes the httpOnly', ({ assert }) => {
    instance.httpOnly = false
    const result = instance.toJSON()
    assert.equal(result.httpOnly, instance.httpOnly)
  })

  test('serializes the secure', ({ assert }) => {
    instance.secure = false
    const result = instance.toJSON()
    assert.equal(result.secure, instance.secure)
  })

  test('serializes the session', ({ assert }) => {
    instance.session = false
    const result = instance.toJSON()
    assert.equal(result.session, instance.session)
  })

  test('serializes the path', ({ assert }) => {
    instance.path = '/abc'
    const result = instance.toJSON()
    assert.equal(result.path, instance.path)
  })
})

test.group('HttpCookie toString()', (group) => {
  let instance: HttpCookie

  group.each.setup(() => {
    instance = new HttpCookie({ name: 'test-name', value: 'test-value' })
  })

  test('returns cookie header string', ({ assert }) => {
    const result = instance.toString()
    assert.equal(result, 'test-name=test-value')
  })
})

test.group('HttpCookie toHeader()', (group) => {
  let instance: HttpCookie
  const base = 'test-name=test-value'

  group.each.setup(() => {
    instance = new HttpCookie({ name: 'test-name', value: 'test-value' })
  })

  test('returns the basic cookie header string', ({ assert }) => {
    const result = instance.toHeader()
    assert.equal(result, base)
  })

  test('adds the path', ({ assert }) => {
    instance.path = '/api'
    const result = instance.toHeader()
    assert.equal(result, `${base}; path=/api`)
  })

  test('adds the domain', ({ assert }) => {
    instance.domain = 'api.com'
    const result = instance.toHeader()
    assert.equal(result, `${base}; domain=api.com`)
  })

  test('adds the httpOnly', ({ assert }) => {
    instance.httpOnly = true
    const result = instance.toHeader()
    assert.equal(result, `${base}; httpOnly=true`)
  })

  test('adds the expires', ({ assert }) => {
    const now = new Date()
    const isoDate = now.toISOString()
    const utcDate = now.toUTCString()
    instance.expires = isoDate

    const result = instance.toHeader()
    assert.equal(result, `${base}; expires=${utcDate}`)
  })

  test('adds the secure', ({ assert }) => {
    instance.secure = true

    const result = instance.toHeader()
    assert.equal(result, `${base}; Secure`)
  })

  test('adds the SameSite=Lax', ({ assert }) => {
    instance.sameSite = 'lax'

    const result = instance.toHeader()
    assert.equal(result, `${base}; SameSite=Lax`)
  })

  test('adds the SameSite=None', ({ assert }) => {
    instance.sameSite = 'no_restriction'

    const result = instance.toHeader()
    assert.equal(result, `${base}; SameSite=None`)
  })

  test('adds the SameSite=Strict', ({ assert }) => {
    instance.sameSite = 'strict'

    const result = instance.toHeader()
    assert.equal(result, `${base}; SameSite=Strict; Secure`)
  })
})
