import 'dotenv/config'
import { describe, expect, it } from 'vitest'

import { createEqlPayload, getPlaintext, eql, LockContext } from '../src'
import type { CsPlaintextV1Schema } from '../src/cs_plaintext_v1'

describe('createEqlPayload', () => {
  it('should create a payload with the correct default values', () => {
    const result = createEqlPayload({
      plaintext: 'test',
      table: 'users',
      column: 'email',
    })

    const expectedPayload: CsPlaintextV1Schema = {
      v: 1,
      k: 'pt',
      p: 'test',
      i: {
        t: 'users',
        c: 'email',
      },
    }

    expect(result).toEqual(expectedPayload)
  })

  it('should set custom schemaVersion and queryType values when provided', () => {
    const result = createEqlPayload({
      plaintext: 'test',
      table: 'users',
      column: 'email',
      schemaVersion: 2,
      queryType: 'match',
    })

    const expectedPayload: CsPlaintextV1Schema = {
      v: 2,
      k: 'pt',
      p: 'test',
      i: {
        t: 'users',
        c: 'email',
      },
      q: 'match',
    }

    expect(result).toEqual(expectedPayload)
  })

  it('should set plaintext to an empty string if undefined', () => {
    const result = createEqlPayload({
      plaintext: '',
      table: 'users',
      column: 'email',
    })

    expect(result.p).toBe('')
  })
})

describe('getPlaintext', () => {
  it('should return plaintext if payload is valid and key is "pt"', () => {
    const payload: CsPlaintextV1Schema = {
      v: 1,
      k: 'pt',
      p: 'test',
      i: {
        t: 'users',
        c: 'email',
      },
    }

    const result = getPlaintext(payload)

    expect(result).toEqual({
      failure: false,
      plaintext: 'test',
    })
  })

  it('should return an error if payload is missing "p" or key is not "pt"', () => {
    const invalidPayload = {
      v: 1,
      k: 'ct',
      c: 'ciphertext',
      p: '',
      i: {
        t: 'users',
        c: 'email',
      },
    }

    const result = getPlaintext(
      invalidPayload as unknown as CsPlaintextV1Schema,
    )

    expect(result).toEqual({
      failure: true,
      error: new Error('No plaintext data found in the EQL payload'),
    })
  })

  it('should return an error and log if payload is invalid', () => {
    const result = getPlaintext(null as unknown as CsPlaintextV1Schema)

    expect(result).toEqual({
      failure: true,
      error: new Error('No plaintext data found in the EQL payload'),
    })
  })
})

describe('encryption and decryption', () => {
  it('should have all required environment variables defined', () => {
    expect(process.env.CS_CLIENT_ID).toBeDefined()
    expect(process.env.CS_CLIENT_KEY).toBeDefined()
    expect(process.env.CS_CLIENT_ACCESS_KEY).toBeDefined()
    expect(process.env.CS_WORKSPACE_ID).toBeDefined()
  })

  it('should encrypt and decrypt a payload', async () => {
    const eqlClient = await eql()

    const ciphertext = await eqlClient.encrypt('plaintext', {
      column: 'column_name',
      table: 'users',
    })

    const plaintext = await eqlClient.decrypt(ciphertext)

    expect(plaintext).toEqual('plaintext')
  }, 30000)

  it('should return null if plaintext is null', async () => {
    const eqlClient = await eql()

    const ciphertext = await eqlClient.encrypt(null, {
      column: 'column_name',
      table: 'users',
    })

    const plaintext = await eqlClient.decrypt(ciphertext)

    expect(plaintext).toEqual(null)
  }, 30000)
})

describe('bulk encryption', () => {
  it('should bulk encrypt and decrypt a payload', async () => {
    const eqlClient = await eql()
    const ciphertexts = await eqlClient.bulkEncrypt(
      [
        {
          plaintext: 'test',
          id: '1',
        },
        {
          plaintext: 'test2',
          id: '2',
        },
      ],
      {
        table: 'users',
        column: 'column_name',
      },
    )

    const plaintexts = await eqlClient.bulkDecrypt(ciphertexts)

    expect(plaintexts).toEqual([
      {
        plaintext: 'test',
        id: '1',
      },
      {
        plaintext: 'test2',
        id: '2',
      },
    ])
  }, 30000)

  it('should return null if plaintexts is empty', async () => {
    const eqlClient = await eql()
    const ciphertexts = await eqlClient.bulkEncrypt([], {
      table: 'users',
      column: 'column_name',
    })
    expect(ciphertexts).toEqual(null)
  }, 30000)

  it('should return null if decrypting empty ciphertexts', async () => {
    const eqlClient = await eql()
    const ciphertexts = null
    const plaintexts = await eqlClient.bulkDecrypt(ciphertexts)
    expect(plaintexts).toEqual(null)
  }, 30000)
})

// ------------------------
// TODO get bulk Encryption/Decryption working in CI.
// These tests pass locally, given you provide a valid JWT.
// To manually test locally, uncomment the following lines and provide a valid JWT in the userJwt variable.
// ------------------------
// const userJwt = ''
// describe('encryption and decryption with lock context', () => {
//   it('should encrypt and decrypt a payload with lock context', async () => {
//     const eqlClient = await eql()

//     const lc = new LockContext()
//     const lockContext = await lc.identify(userJwt)

//     const ciphertext = await eqlClient
//       .encrypt('plaintext', {
//         column: 'column_name',
//         table: 'users',
//       })
//       .withLockContext(lockContext)

//     const plaintext = await eqlClient
//       .decrypt(ciphertext)
//       .withLockContext(lockContext)

//     expect(plaintext).toEqual('plaintext')
//   }, 30000)

//   it('should encrypt with context and be unable to decrypt without context', async () => {
//     const eqlClient = await eql()

//     const lc = new LockContext()
//     const lockContext = await lc.identify(userJwt)

//     const ciphertext = await eqlClient
//       .encrypt('plaintext', {
//         column: 'column_name',
//         table: 'users',
//       })
//       .withLockContext(lockContext)

//     try {
//       await eqlClient.decrypt(ciphertext)
//     } catch (error) {
//       const e = error as Error
//       expect(e.message.startsWith('Failed to retrieve key')).toEqual(true)
//     }
//   }, 30000)

//   it('should bulk encrypt and decrypt a payload with lock context', async () => {
//     const eqlClient = await eql()

//     const lc = new LockContext()
//     const lockContext = await lc.identify(userJwt)

//     const ciphertexts = await eqlClient
//       .bulkEncrypt(
//         [
//           {
//             plaintext: 'test',
//             id: '1',
//           },
//           {
//             plaintext: 'test2',
//             id: '2',
//           },
//         ],
//         {
//           table: 'users',
//           column: 'column_name',
//         },
//       )
//       .withLockContext(lockContext)

//     const plaintexts = await eqlClient
//       .bulkDecrypt(ciphertexts)
//       .withLockContext(lockContext)

//     expect(plaintexts).toEqual([
//       {
//         plaintext: 'test',
//         id: '1',
//       },
//       {
//         plaintext: 'test2',
//         id: '2',
//       },
//     ])
//   }, 30000)
// })
