import { TestBed } from '@automock/jest'
import { Reservation, Table, TableCombination } from '@backend/domain'
import { TableCombinationRepository, TableRepository } from '@backend/repository'
import { randomUUID } from 'node:crypto'
import { TableAssignmentService } from './table-assignment.service'

const tables = {
	two: new Table({ id: randomUUID(), capacity: 2 }),
	three: new Table({ id: randomUUID(), capacity: 3 }),
	four: new Table({ id: randomUUID(), capacity: 4 }),
	seven: new Table({ id: randomUUID(), capacity: 7 }),
	five: new Table({ id: randomUUID(), capacity: 5 }),
}

describe('Table Assignment Service', () => {
	let service: TableAssignmentService
	let tableRepository: jest.Mocked<TableRepository>
	let tableCombinationRepository: jest.Mocked<TableCombinationRepository>
	beforeAll(() => {
		const { unit, unitRef } = TestBed.create(TableAssignmentService).compile()
		service = unit
		tableRepository = unitRef.get(TableRepository)
		tableCombinationRepository = unitRef.get(TableCombinationRepository)
	})
	beforeEach(() => {
		jest.clearAllMocks()
	})
	xdescribe('findBestTableCombination method', () => {
		it.each([
			{
				noOfPersons: 6,
				tables: [tables.two, tables.four, tables.three, tables.seven],
				expected: [tables.two, tables.four],
			},
			{
				noOfPersons: 10,
				tables: [tables.five, tables.three, tables.seven, tables.five],
				expected: [tables.five, tables.five],
			},
			{
				noOfPersons: 3,
				tables: [tables.five, tables.four],
				expected: [tables.four],
			},
			{
				noOfPersons: 8,
				tables: [tables.two, tables.two, tables.two, tables.two],
				expected: [tables.two, tables.two, tables.two, tables.two],
			},
			{
				noOfPersons: 6,
				tables: [tables.three, tables.two, tables.four, tables.three],
				expected: [tables.three, tables.three],
			},
		])('should find tables with least capacity', ({ noOfPersons, tables, expected }) => {
			const result = TableAssignmentService.prototype['findBestTableCombination'].call(service, tables, { noOfPersons })
			expect(result).toEqual(expected)
		})
		describe('findAvailableTableCombination method', () => {
			it.each([
				{
					noOfPersons: 6,
					combinations: [
						new TableCombination({ tables: [tables.two, tables.two] }),
						new TableCombination({ tables: [tables.two, tables.four] }),
						new TableCombination({ tables: [tables.three, tables.two] }),
					],
					reservations: [],
					expected: [tables.two, tables.four],
				},
				{
					noOfPersons: 5,
					combinations: [
						new TableCombination({ tables: [tables.two, tables.two] }),
						new TableCombination({ tables: [tables.three, tables.two] }),
					],
					reservations: [],
					expected: [tables.three, tables.two],
				},
			])(
				'should filter tables that do not fit no of persons',
				({ noOfPersons, combinations, reservations, expected }) => {
					const result = TableAssignmentService.prototype['findAvailableTableCombination'].call(
						service,
						noOfPersons,
						combinations,
						reservations,
					)
					expect(result).toEqual(expected)
				},
			)
			it.each([
				{
					noOfPersons: 6,
					combinations: [
						new TableCombination({ tables: [tables.two, tables.two] }),
						new TableCombination({ tables: [tables.two, tables.four] }),
						new TableCombination({ tables: [tables.seven, tables.two] }),
					],
					reservations: [new Reservation({ tables: [tables.two] })],
					expected: [tables.seven],
				},
				{
					noOfPersons: 3,
					combinations: [
						new TableCombination({ tables: [tables.two, tables.two] }),
						new TableCombination({ tables: [tables.three, tables.two] }),
					],
					reservations: [new Reservation({ tables: [tables.two] })],
					expected: [tables.three],
				},
			])('should filter taken tables from combinations', ({ noOfPersons, combinations, reservations, expected }) => {
				const result = TableAssignmentService.prototype['findAvailableTableCombination'].call(
					service,
					noOfPersons,
					combinations,
					reservations,
				)
				expect(result).toEqual(expected)
			})
		})
		describe('findAvailableTable method', () => {
			it.each([
				{
					noOfPersons: 6,
					tables: [tables.two, tables.seven],
					reservations: [new Reservation({ tables: [tables.two] })],
					expected: tables.seven,
				},
				{
					noOfPersons: 5,
					tables: [tables.two, tables.three],
					reservations: [new Reservation({ tables: [tables.four] })],
					expected: undefined,
				},
			])('should filter taken tables', ({ noOfPersons, tables, reservations, expected }) => {
				const result = TableAssignmentService.prototype['findAvailableTable'].call(
					service,
					noOfPersons,
					tables,
					reservations,
				)
				expect(result).toEqual(expected)
			})
		})
		describe('getAvailableTables method', () => {
			it.each([
				{
					label: 'should return an available table',
					noOfPersons: 2,
					tables: [tables.two, tables.four],
					combinations: [],
					reservations: [],
					expected: [tables.two],
				},
				{
					label: 'should return an available table combination',
					noOfPersons: 10,
					tables: [tables.two, tables.three],
					combinations: [new TableCombination({ tables: [tables.seven, tables.three] })],
					reservations: [],
					expected: [tables.seven, tables.three],
				},
				{
					label: 'should return an available table combination when there are taken tables',
					noOfPersons: 5,
					tables: [tables.two, tables.five],
					combinations: [new TableCombination({ tables: [tables.four, tables.three] })],
					reservations: [new Reservation({ tables: [tables.two] }), new Reservation({ tables: [tables.five] })],
					expected: [tables.four, tables.three],
				},
			])('$label', async ({ noOfPersons, tables, combinations, reservations, expected }) => {
				tableRepository.maximum.mockResolvedValue(
					tables.map((table) => table.capacity).reduce((acc, cur) => acc + cur, 0),
				)
				tableRepository.find.mockResolvedValue(tables)
				tableCombinationRepository.find.mockResolvedValue(combinations)
				const result = await TableAssignmentService.prototype['getAvailableTables'].call(
					service,
					noOfPersons,
					reservations,
				)
				expect(result).toEqual(expected)
			})
		})
	})
})
