import { TestBed } from '@automock/jest'
import { CustomerData, Reservation, ReservationSource, Table } from '@backend/domain'
import { ReservationRepository } from '@backend/repository'
import { addDays, addMinutes } from 'date-fns'
import { randomUUID } from 'node:crypto'
import { ReservationUtilsService, TableAssignmentService } from '../shared'
import { CreateReservationRequest } from '../types'
import { ReservationService } from './reservation.service'

jest.mock('typeorm-transactional', () => ({
	Transactional: () => () => undefined,
}))

function request(customerData: Partial<CustomerData>): CreateReservationRequest {
	return {
		noOfPersons: 2,
		startDate: addDays(new Date(), 1),
		endDate: addMinutes(addDays(new Date(), 1), 120),
		customerData: customerData as CustomerData,
	} as CreateReservationRequest
}

describe('ReservationService.create (management token + name split)', () => {
	let service: ReservationService
	let reservationRepository: jest.Mocked<ReservationRepository>
	let tableAssignmentService: jest.Mocked<TableAssignmentService>
	let utilsService: jest.Mocked<ReservationUtilsService>

	beforeAll(() => {
		const { unit, unitRef } = TestBed.create(ReservationService).compile()
		service = unit
		reservationRepository = unitRef.get(ReservationRepository)
		tableAssignmentService = unitRef.get(TableAssignmentService)
		utilsService = unitRef.get(ReservationUtilsService)
	})

	beforeEach(() => {
		jest.clearAllMocks()
		tableAssignmentService.assignTable.mockResolvedValue({ tables: [{ id: randomUUID(), capacity: 4 }] as Table[] })
		utilsService.canAutoApproveReservation.mockResolvedValue(true)
		reservationRepository.save.mockImplementation(async (entity) => entity as Reservation)
		utilsService.notifyNewReservation.mockReturnValue(undefined)
	})

	it('generates a unique, URL-safe management token for every new reservation', async () => {
		await service.create(request({ name: 'Maxi Musterperson' }), ReservationSource.widget)

		const saved = reservationRepository.save.mock.calls[0][0] as Partial<Reservation>
		expect(saved.managementToken).toMatch(/^[\w-]{43}$/)

		await service.create(request({ name: 'Maxi Musterperson' }), ReservationSource.widget)
		const saved2 = reservationRepository.save.mock.calls[1][0] as Partial<Reservation>
		expect(saved2.managementToken).not.toBe(saved.managementToken)
	})

	it('derives name from firstName and lastName when provided', async () => {
		await service.create(
			request({ name: 'ignored', firstName: '  Maxi ', lastName: ' Musterperson ' }),
			ReservationSource.widget,
		)

		const saved = reservationRepository.save.mock.calls[0][0] as Partial<Reservation>
		expect(saved.customerData?.name).toBe('Maxi Musterperson')
		expect(saved.customerData?.firstName).toBe('Maxi')
		expect(saved.customerData?.lastName).toBe('Musterperson')
	})

	it('supports lastName-only guests (first name is optional)', async () => {
		await service.create(request({ name: 'fallback', lastName: 'Musterperson' }), ReservationSource.widget)

		const saved = reservationRepository.save.mock.calls[0][0] as Partial<Reservation>
		expect(saved.customerData?.name).toBe('Musterperson')
		expect(saved.customerData?.firstName).toBeUndefined()
	})

	it('keeps plain name payloads working unchanged (backward compatibility)', async () => {
		await service.create(request({ name: 'Maxi Musterperson' }), ReservationSource.admin)

		const saved = reservationRepository.save.mock.calls[0][0] as Partial<Reservation>
		expect(saved.customerData?.name).toBe('Maxi Musterperson')
		expect(saved.customerData?.firstName).toBeUndefined()
		expect(saved.customerData?.lastName).toBeUndefined()
	})

	it('returns the saved entity including the token so the widget can show the manage link', async () => {
		const entity = await service.create(request({ name: 'Maxi Musterperson' }), ReservationSource.widget)
		expect(entity.managementToken).toBeDefined()
	})
})
