import { faker } from '@faker-js/faker'
import { PrismaClient } from '@prisma/client'
import { afterAll, afterEach, describe, expect, test } from 'vitest'
import { factoryExtension } from './factory'

describe('Factory', () => {
  const xprisma = new PrismaClient().$extends(factoryExtension)

  const UserFactory = xprisma.user.$factory({
    firstName: 'Foo',
    lastName: 'Bar',
  })

  afterEach(async () => {
    await xprisma.post.deleteMany()
    await xprisma.user.deleteMany()
  })

  afterAll(async () => {
    await xprisma.$disconnect()
  })

  describe('.$factory', () => {
    test('should create FactoryBuilder functions', () => {
      expect(UserFactory.attributes).toBeTypeOf('function')
      expect(UserFactory.create).toBeTypeOf('function')
    })

    test('lazy evaluation', () => {
      const Factory = xprisma.user.$factory({
        firstName: () => faker.name.firstName(),
        lastName: 'Bar',
      })

      expect(Factory.attributes().firstName).toBeTypeOf('string')
      expect(Factory.attributes().firstName).not.toEqual(Factory.attributes().firstName)
    })
  })

  describe('factory.attributes', () => {
    test('defaults', () => {
      expect(UserFactory.attributes()).toEqual({
        firstName: 'Foo',
        lastName: 'Bar',
      })
    })

    test('basic args', () => {
      const user = UserFactory.attributes({
        firstName: 'Baz',
      })

      expect(user).toEqual({
        firstName: 'Baz',
        lastName: 'Bar',
      })
    })

    test('lazy evaluation', () => {
      const user = UserFactory.attributes({
        firstName: () => faker.name.firstName(),
      })

      expect(user.firstName).not.toEqual('Foo')
      expect(user.firstName).toEqual(expect.any(String))
      expect(user.lastName).toStrictEqual('Bar')
    })
  })

  describe('factory.create', () => {
    test('defaults', async () => {
      const user = await UserFactory.create()

      expect(user).toEqual({
        id: expect.any(Number),
        firstName: 'Foo',
        lastName: 'Bar',
        createdAt: expect.any(Date),
        updatedAt: expect.any(Date),
        deletedAt: null,
      })
    })

    test('basic args', async () => {
      const user = await UserFactory.create({
        firstName: 'Baz',
      })

      expect(user).toEqual({
        id: expect.any(Number),
        firstName: 'Baz',
        lastName: 'Bar',
        createdAt: expect.any(Date),
        updatedAt: expect.any(Date),
        deletedAt: null,
      })
    })

    test('lazy evaluation', async () => {
      const user = await UserFactory.create({
        firstName: () => faker.name.firstName(),
      })

      expect(user.firstName).not.toEqual('Foo')

      expect(user).toEqual({
        id: expect.any(Number),
        firstName: expect.any(String),
        lastName: 'Bar',
        createdAt: expect.any(Date),
        updatedAt: expect.any(Date),
        deletedAt: null,
      })
    })

    describe('with relations', () => {
      test('.create with initial definition', async () => {
        const user = UserFactory.attributes({
          firstName: 'Baz',
        })

        const PostFactory = xprisma.post.$factory({
          title: 'Title',
          content: 'Lorem Ipsum',
          author: {
            create: user,
          },
        })

        const post = await PostFactory.create({
          author: {
            create: user,
          },
        })

        expect(post).toEqual({
          id: expect.any(Number),
          title: 'Title',
          content: 'Lorem Ipsum',
          commentCount: 0,
          authorId: expect.any(Number),
          author: {
            id: expect.any(Number),
            firstName: 'Baz',
            lastName: 'Bar',
            createdAt: expect.any(Date),
            updatedAt: expect.any(Date),
            deletedAt: null,
          },
          createdAt: expect.any(Date),
          updatedAt: expect.any(Date),
          deletedAt: null,
        })
      })

      test('setting relations without initial definition', async () => {
        const user = UserFactory.attributes()

        const PostFactory = xprisma.post.$factory({
          title: 'Title',
          content: 'Lorem Ipsum',
          author: {
            create: user,
          },
        })

        const user2 = await UserFactory.create()

        await PostFactory.create({
          author: {
            connect: { id: user2.id },
          },
        })
      })
    })
  })
})
