import type { NextRESTClient } from 'helpers/NextRESTClient.js'
import type { Payload, User } from 'payload'

import path from 'path'
import { fileURLToPath } from 'url'

import { devUser, regularUser } from '../credentials.js'
import { initPayloadInt } from '../helpers/initPayloadInt.js'

const queryPresetsCollectionSlug = 'payload-query-presets'

let payload: Payload
let restClient: NextRESTClient
let user: User
let user2: User
let anonymousUser: User

const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)

describe('Query Presets', () => {
  beforeAll(async () => {
    // @ts-expect-error: initPayloadInt does not have a proper type definition
    ;({ payload, restClient } = await initPayloadInt(dirname))

    user = await payload
      .login({
        collection: 'users',
        data: {
          email: devUser.email,
          password: devUser.password,
        },
      })
      ?.then((result) => result.user)

    user2 = await payload
      .login({
        collection: 'users',
        data: {
          email: regularUser.email,
          password: regularUser.password,
        },
      })
      ?.then((result) => result.user)

    anonymousUser = await payload
      .login({
        collection: 'users',
        data: {
          email: 'anonymous@email.com',
          password: regularUser.password,
        },
      })
      ?.then((result) => result.user)
  })

  afterAll(async () => {
    if (typeof payload.db.destroy === 'function') {
      await payload.db.destroy()
    }
  })

  describe('default access control', () => {
    it('should only allow logged in users to perform actions', async () => {
      // create
      try {
        const result = await payload.create({
          collection: queryPresetsCollectionSlug,
          user: undefined,
          overrideAccess: false,
          data: {
            title: 'Only Logged In Users',
            relatedCollection: 'pages',
          },
        })

        expect(result).toBeFalsy()
      } catch (error: unknown) {
        expect((error as Error).message).toBe('You are not allowed to perform this action.')
      }

      const { id } = await payload.create({
        collection: queryPresetsCollectionSlug,
        data: {
          title: 'Only Logged In Users',
          relatedCollection: 'pages',
        },
      })

      // read
      try {
        const result = await payload.findByID({
          collection: queryPresetsCollectionSlug,
          depth: 0,
          user: undefined,
          overrideAccess: false,
          id,
        })

        expect(result).toBeFalsy()
      } catch (error: unknown) {
        expect((error as Error).message).toBe('You are not allowed to perform this action.')
      }

      // update
      try {
        const result = await payload.update({
          collection: queryPresetsCollectionSlug,
          id,
          user: undefined,
          overrideAccess: false,
          data: {
            title: 'Only Logged In Users (Updated)',
          },
        })

        expect(result).toBeFalsy()
      } catch (error: unknown) {
        expect((error as Error).message).toBe('You are not allowed to perform this action.')

        // make sure the update didn't go through
        const preset = await payload.findByID({
          collection: queryPresetsCollectionSlug,
          depth: 0,
          id,
        })

        expect(preset.title).toBe('Only Logged In Users')
      }

      // delete
      try {
        const result = await payload.delete({
          collection: queryPresetsCollectionSlug,
          id: 'some-id',
          user: undefined,
          overrideAccess: false,
        })

        expect(result).toBeFalsy()
      } catch (error: unknown) {
        expect((error as Error).message).toBe('You are not allowed to perform this action.')

        // make sure the delete didn't go through
        const preset = await payload.findByID({
          collection: queryPresetsCollectionSlug,
          depth: 0,
          id,
        })

        expect(preset.title).toBe('Only Logged In Users')
      }
    })

    it('should respect access when set to "specificUsers"', async () => {
      const presetForSpecificUsers = await payload.create({
        collection: queryPresetsCollectionSlug,
        user,
        data: {
          title: 'Specific Users',
          where: {
            text: {
              equals: 'example page',
            },
          },
          access: {
            read: {
              constraint: 'specificUsers',
              users: [user.id],
            },
            update: {
              constraint: 'specificUsers',
              users: [user.id],
            },
          },
          relatedCollection: 'pages',
        },
      })

      const foundPresetWithUser1 = await payload.findByID({
        collection: queryPresetsCollectionSlug,
        depth: 0,
        user,
        overrideAccess: false,
        id: presetForSpecificUsers.id,
      })

      expect(foundPresetWithUser1.id).toBe(presetForSpecificUsers.id)

      try {
        const foundPresetWithUser2 = await payload.findByID({
          collection: queryPresetsCollectionSlug,
          depth: 0,
          user: user2,
          overrideAccess: false,
          id: presetForSpecificUsers.id,
        })

        expect(foundPresetWithUser2).toBeFalsy()
      } catch (error: unknown) {
        expect((error as Error).message).toBe('Not Found')
      }

      const presetUpdatedByUser1 = await payload.update({
        collection: queryPresetsCollectionSlug,
        id: presetForSpecificUsers.id,
        user,
        overrideAccess: false,
        data: {
          title: 'Specific Users (Updated)',
        },
      })

      expect(presetUpdatedByUser1.title).toBe('Specific Users (Updated)')

      try {
        const presetUpdatedByUser2 = await payload.update({
          collection: queryPresetsCollectionSlug,
          id: presetForSpecificUsers.id,
          user: user2,
          overrideAccess: false,
          data: {
            title: 'Specific Users (Updated)',
          },
        })

        expect(presetUpdatedByUser2).toBeFalsy()
      } catch (error: unknown) {
        expect((error as Error).message).toBe('You are not allowed to perform this action.')
      }
    })

    it('should respect access when set to "onlyMe"', async () => {
      // create a new doc so that the creating user is the owner
      const presetForOnlyMe = await payload.create({
        collection: queryPresetsCollectionSlug,
        user,
        data: {
          title: 'Only Me',
          where: {
            text: {
              equals: 'example page',
            },
          },
          access: {
            read: {
              constraint: 'onlyMe',
            },
            update: {
              constraint: 'onlyMe',
            },
          },
          relatedCollection: 'pages',
        },
      })

      const foundPresetWithUser1 = await payload.findByID({
        collection: queryPresetsCollectionSlug,
        depth: 0,
        user,
        overrideAccess: false,
        id: presetForOnlyMe.id,
      })

      expect(foundPresetWithUser1.id).toBe(presetForOnlyMe.id)

      try {
        const foundPresetWithUser2 = await payload.findByID({
          collection: queryPresetsCollectionSlug,
          depth: 0,
          user: user2,
          overrideAccess: false,
          id: presetForOnlyMe.id,
        })

        expect(foundPresetWithUser2).toBeFalsy()
      } catch (error: unknown) {
        expect((error as Error).message).toBe('Not Found')
      }

      const presetUpdatedByUser1 = await payload.update({
        collection: queryPresetsCollectionSlug,
        id: presetForOnlyMe.id,
        user,
        overrideAccess: false,
        data: {
          title: 'Only Me (Updated)',
        },
      })

      expect(presetUpdatedByUser1.title).toBe('Only Me (Updated)')

      try {
        const presetUpdatedByUser2 = await payload.update({
          collection: queryPresetsCollectionSlug,
          id: presetForOnlyMe.id,
          user: user2,
          overrideAccess: false,
          data: {
            title: 'Only Me (Updated)',
          },
        })

        expect(presetUpdatedByUser2).toBeFalsy()
      } catch (error: unknown) {
        expect((error as Error).message).toBe('You are not allowed to perform this action.')
      }
    })

    it('should respect access when set to "everyone"', async () => {
      const presetForEveryone = await payload.create({
        collection: queryPresetsCollectionSlug,
        user,
        data: {
          title: 'Everyone',
          where: {
            text: {
              equals: 'example page',
            },
          },
          access: {
            read: {
              constraint: 'everyone',
            },
            update: {
              constraint: 'everyone',
            },
            delete: {
              constraint: 'everyone',
            },
          },
          relatedCollection: 'pages',
        },
      })

      const foundPresetWithUser1 = await payload.findByID({
        collection: queryPresetsCollectionSlug,
        depth: 0,
        user,
        overrideAccess: false,
        id: presetForEveryone.id,
      })

      expect(foundPresetWithUser1.id).toBe(presetForEveryone.id)

      const foundPresetWithUser2 = await payload.findByID({
        collection: queryPresetsCollectionSlug,
        depth: 0,
        user: user2,
        overrideAccess: false,
        id: presetForEveryone.id,
      })

      expect(foundPresetWithUser2.id).toBe(presetForEveryone.id)

      const presetUpdatedByUser1 = await payload.update({
        collection: queryPresetsCollectionSlug,
        id: presetForEveryone.id,
        user,
        overrideAccess: false,
        data: {
          title: 'Everyone (Update 1)',
        },
      })

      expect(presetUpdatedByUser1.title).toBe('Everyone (Update 1)')

      const presetUpdatedByUser2 = await payload.update({
        collection: queryPresetsCollectionSlug,
        id: presetForEveryone.id,
        user: user2,
        overrideAccess: false,
        data: {
          title: 'Everyone (Update 2)',
        },
      })

      expect(presetUpdatedByUser2.title).toBe('Everyone (Update 2)')
    })
  })

  describe('user-defined access control', () => {
    it('should respect top-level access control overrides', async () => {
      const preset = await payload.create({
        collection: queryPresetsCollectionSlug,
        user,
        data: {
          title: 'Top-Level Access Control Override',
          relatedCollection: 'pages',
          access: {
            read: {
              constraint: 'everyone',
            },
            update: {
              constraint: 'everyone',
            },
            delete: {
              constraint: 'everyone',
            },
          },
        },
      })

      const foundPresetWithUser1 = await payload.findByID({
        collection: queryPresetsCollectionSlug,
        depth: 0,
        user,
        overrideAccess: false,
        id: preset.id,
      })

      expect(foundPresetWithUser1.id).toBe(preset.id)

      try {
        const foundPresetWithAnonymousUser = await payload.findByID({
          collection: queryPresetsCollectionSlug,
          depth: 0,
          user: anonymousUser,
          overrideAccess: false,
          id: preset.id,
        })

        expect(foundPresetWithAnonymousUser).toBeFalsy()
      } catch (error: unknown) {
        expect((error as Error).message).toBe('You are not allowed to perform this action.')
      }
    })

    it('should respect access when set to "specificRoles"', async () => {
      const presetForSpecificRoles = await payload.create({
        collection: queryPresetsCollectionSlug,
        user,
        data: {
          title: 'Specific Roles',
          where: {
            text: {
              equals: 'example page',
            },
          },
          access: {
            read: {
              constraint: 'specificRoles',
              roles: ['admin'],
            },
            update: {
              constraint: 'specificRoles',
              roles: ['admin'],
            },
          },
          relatedCollection: 'pages',
        },
      })

      const foundPresetWithUser1 = await payload.findByID({
        collection: queryPresetsCollectionSlug,
        depth: 0,
        user,
        overrideAccess: false,
        id: presetForSpecificRoles.id,
      })

      expect(foundPresetWithUser1.id).toBe(presetForSpecificRoles.id)

      try {
        const foundPresetWithUser2 = await payload.findByID({
          collection: queryPresetsCollectionSlug,
          depth: 0,
          user: user2,
          overrideAccess: false,
          id: presetForSpecificRoles.id,
        })

        expect(foundPresetWithUser2).toBeFalsy()
      } catch (error: unknown) {
        expect((error as Error).message).toBe('Not Found')
      }

      const presetUpdatedByUser1 = await payload.update({
        collection: queryPresetsCollectionSlug,
        id: presetForSpecificRoles.id,
        user,
        overrideAccess: false,
        data: {
          title: 'Specific Roles (Updated)',
        },
      })

      expect(presetUpdatedByUser1.title).toBe('Specific Roles (Updated)')

      try {
        const presetUpdatedByUser2 = await payload.update({
          collection: queryPresetsCollectionSlug,
          id: presetForSpecificRoles.id,
          user: user2,
          overrideAccess: false,
          data: {
            title: 'Specific Roles (Updated)',
          },
        })

        expect(presetUpdatedByUser2).toBeFalsy()
      } catch (error: unknown) {
        expect((error as Error).message).toBe('You are not allowed to perform this action.')
      }
    })

    it('should respect boolean access control results', async () => {
      // create a preset with the read constraint set to "noone"
      const presetForNoone = await payload.create({
        collection: queryPresetsCollectionSlug,
        user,
        data: {
          relatedCollection: 'pages',
          title: 'Noone',
          where: {
            text: {
              equals: 'example page',
            },
          },
          access: {
            read: {
              constraint: 'noone',
            },
          },
        },
      })

      try {
        const foundPresetWithUser1 = await payload.findByID({
          collection: queryPresetsCollectionSlug,
          depth: 0,
          user,
          overrideAccess: false,
          id: presetForNoone.id,
        })

        expect(foundPresetWithUser1).toBeFalsy()
      } catch (error: unknown) {
        expect((error as Error).message).toBe('Not Found')
      }
    })
  })

  it.skip('should disable query presets when "enabledQueryPresets" is not true on the collection', async () => {
    try {
      const result = await payload.create({
        collection: 'payload-query-presets',
        user,
        data: {
          title: 'Disabled Query Presets',
          relatedCollection: 'pages',
        },
      })

      // TODO: this test always passes because this expect throws an error which is caught and passes the 'catch' block
      expect(result).toBeFalsy()
    } catch (error) {
      expect(error).toBeDefined()
    }
  })

  describe('Where object formatting', () => {
    it('transforms "where" query objects into the "and" / "or" format', async () => {
      const result = await payload.create({
        collection: queryPresetsCollectionSlug,
        user,
        data: {
          title: 'Where Object Formatting',
          where: {
            text: {
              equals: 'example page',
            },
          },
          access: {
            read: {
              constraint: 'everyone',
            },
            update: {
              constraint: 'everyone',
            },
            delete: {
              constraint: 'everyone',
            },
          },
          relatedCollection: 'pages',
        },
      })

      expect(result.where).toMatchObject({
        or: [
          {
            and: [
              {
                text: {
                  equals: 'example page',
                },
              },
            ],
          },
        ],
      })
    })
  })
})
