import { Reservation, Table, TableCombination } from '@backend/domain'
import { getAllPartySizes, getOpenTableCount } from './table.utils'

const table = (id: string, capacity: number): Table => new Table({ id, capacity })
const combination = (tables: Table[]): TableCombination => new TableCombination({ tables })
const reservationOn = (tables: Table[]): Reservation => new Reservation({ tables })

describe('Table Utils', () => {
	describe('getAllPartySizes', () => {
		const tables: Table[] = [
			new Table({ capacity: 2 }),
			new Table({ capacity: 2 }),
			new Table({ capacity: 4 }),
			new Table({ capacity: 6 }),
			new Table({ capacity: 6 }),
			new Table({ capacity: 6 }),
		]

		it('should correctly count partySizes with no of tables', () => {
			const result = getAllPartySizes(tables)
			expect(result).toEqual(
				new Map([
					[1, 6],
					[2, 6],
					[3, 4],
					[4, 4],
					[5, 3],
					[6, 3],
				]),
			)
		})

		it('should not change single-table counts when combinations are passed', () => {
			// A combination capacity (4) at or below the biggest single table (6) must not
			// affect the existing party sizes — feed and lookup stay in sync for those.
			const combinations = [combination([table('a', 2), table('b', 2)])]
			const result = getAllPartySizes(tables, combinations)

			expect(result.get(1)).toBe(6)
			expect(result.get(4)).toBe(4)
			expect(result.get(6)).toBe(3)
			expect(result.has(7)).toBe(false)
		})

		it('should add party sizes above the biggest single table from combinations', () => {
			const t = [table('1', 4), table('2', 4), table('3', 4), table('4', 8)]
			const combinations = [
				combination([t[0], t[1]]), // capacity 8
				combination([t[0], t[1], t[2]]), // capacity 12
				combination([t[0], t[1], t[3]]), // capacity 16
			]

			const result = getAllPartySizes(t, combinations)

			// 1..8 still come from single tables; the 8-capacity combination adds nothing new.
			expect(result.get(8)).toBe(1)
			// 9..12 are served by the 12- and 16-capacity combinations.
			expect(result.get(9)).toBe(2)
			expect(result.get(12)).toBe(2)
			// 13..16 only by the 16-capacity combination.
			expect(result.get(13)).toBe(1)
			expect(result.get(16)).toBe(1)
			expect(result.has(17)).toBe(false)
		})
	})

	describe('getOpenTableCount', () => {
		it('should keep the single-table calculation for party sizes that fit one table', () => {
			const tables = [table('1', 2), table('2', 4), table('3', 6)]
			expect(getOpenTableCount(tables, [], 4)).toBe(2)
			expect(getOpenTableCount(tables, [reservationOn([table('3', 6)])], 4)).toBe(1)
		})

		it('should return 0 for large parties when no combinations are configured', () => {
			const tables = [table('1', 4), table('2', 4)]
			expect(getOpenTableCount(tables, [], 8)).toBe(0)
		})

		it('should count disjoint combinations as separate open slots', () => {
			const tables = [table('1', 4), table('2', 4), table('3', 6), table('4', 6)]
			const combinations = [
				combination([tables[0], tables[1]]), // capacity 8, tables 1+2
				combination([tables[2], tables[3]]), // capacity 12, tables 3+4
			]
			// Party 8: both combinations qualify and share no tables → 2 open slots.
			expect(getOpenTableCount(tables, [], 8, combinations)).toBe(2)
		})

		it('should not double-count combinations that share a table (no overbooking)', () => {
			const t1 = table('1', 4)
			const t2 = table('2', 4)
			const t3 = table('3', 4)
			const tables = [t1, t2, t3]
			const combinations = [
				combination([t1, t2]), // capacity 8
				combination([t1, t2, t3]), // capacity 12, shares t1+t2
			]
			// Party 8: both qualify but share t1+t2 → only one can be seated at a time.
			expect(getOpenTableCount(tables, [], 8, combinations)).toBe(1)
		})

		it('should exclude a combination when one of its tables is already reserved', () => {
			const t1 = table('1', 4)
			const t2 = table('2', 4)
			const t3 = table('3', 6)
			const t4 = table('4', 6)
			const tables = [t1, t2, t3, t4]
			const combinations = [
				combination([t1, t2]), // capacity 8
				combination([t3, t4]), // capacity 12
			]
			// t1 is occupied → first combination unavailable, second still free → 1 open slot.
			expect(getOpenTableCount(tables, [reservationOn([t1])], 8, combinations)).toBe(1)
		})
	})
})
