import { describe, expect, it, vi, beforeEach } from 'vitest';

vi.mock('$lib/shared/api.server', () => ({
	createApiClient: vi.fn(),
	extractErrorCode: (e: unknown) => (e as { code?: string } | undefined)?.code
}));

import { createApiClient } from '$lib/shared/api.server';
import { actions } from './+page.server';

const mockFetch = vi.fn() as unknown as typeof fetch;

beforeEach(() => vi.clearAllMocks());

function makeFormData(overrides: Partial<Record<string, string>> = {}): {
	request: Request;
	redirectThrown: () => unknown;
} {
	const fd = new FormData();
	fd.set('personType', 'PERSON');
	fd.set('firstName', 'Hans');
	fd.set('lastName', 'Müller');
	for (const [k, v] of Object.entries(overrides)) {
		if (v == null) fd.delete(k);
		else fd.set(k, v);
	}
	return {
		request: new Request('http://localhost/persons/p1/edit', { method: 'POST', body: fd }),
		redirectThrown: () => {}
	};
}

describe('persons/[id]/edit update action — generation (#689)', () => {
	it('always includes generation in the PUT body — even when value is 0', async () => {
		const put = vi
			.fn()
			.mockResolvedValue({ response: { ok: true, status: 200 }, data: { id: 'p1' } });
		vi.mocked(createApiClient).mockReturnValue({ PUT: put } as unknown as ReturnType<
			typeof createApiClient
		>);

		const { request } = makeFormData({ generation: '0' });

		try {
			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			await actions.update({ request, params: { id: 'p1' }, fetch: mockFetch } as any);
		} catch {
			// redirect throws on success — ignore
		}

		expect(put).toHaveBeenCalledTimes(1);
		const body = put.mock.calls[0][1].body;
		expect(body).toHaveProperty('generation', 0);
	});

	it('sends generation: null when the dropdown is cleared (empty option)', async () => {
		const put = vi
			.fn()
			.mockResolvedValue({ response: { ok: true, status: 200 }, data: { id: 'p1' } });
		vi.mocked(createApiClient).mockReturnValue({ PUT: put } as unknown as ReturnType<
			typeof createApiClient
		>);

		const { request } = makeFormData({ generation: '' });

		try {
			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			await actions.update({ request, params: { id: 'p1' }, fetch: mockFetch } as any);
		} catch {
			// redirect throws on success — ignore
		}

		expect(put).toHaveBeenCalledTimes(1);
		const body = put.mock.calls[0][1].body;
		expect(body).toHaveProperty('generation', null);
	});

	it('sends generation: 3 when the dropdown carries G 3', async () => {
		const put = vi
			.fn()
			.mockResolvedValue({ response: { ok: true, status: 200 }, data: { id: 'p1' } });
		vi.mocked(createApiClient).mockReturnValue({ PUT: put } as unknown as ReturnType<
			typeof createApiClient
		>);

		const { request } = makeFormData({ generation: '3' });

		try {
			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			await actions.update({ request, params: { id: 'p1' }, fetch: mockFetch } as any);
		} catch {
			// redirect throws on success — ignore
		}

		expect(put).toHaveBeenCalledTimes(1);
		const body = put.mock.calls[0][1].body;
		expect(body).toHaveProperty('generation', 3);
	});
});

describe('persons/[id]/edit relationship actions (#837)', () => {
	function relForm(overrides: Record<string, string | null> = {}): Request {
		const fd = new FormData();
		fd.set('relatedPersonId', 'p2');
		fd.set('relationType', 'SPOUSE_OF');
		for (const [k, v] of Object.entries(overrides)) {
			if (v == null) fd.delete(k);
			else fd.set(k, v);
		}
		return new Request('http://localhost/persons/p1/edit', { method: 'POST', body: fd });
	}

	it('addRelationship posts date + precision + notes', async () => {
		const post = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: {} });
		vi.mocked(createApiClient).mockReturnValue({ POST: post } as unknown as ReturnType<
			typeof createApiClient
		>);
		const request = relForm({
			fromDate: '1923-05-12',
			fromDatePrecision: 'DAY',
			notes: 'Hochzeit'
		});

		// eslint-disable-next-line @typescript-eslint/no-explicit-any
		await actions.addRelationship({ request, params: { id: 'p1' }, fetch: mockFetch } as any);

		const [path, opts] = post.mock.calls[0];
		expect(path).toBe('/api/persons/{id}/relationships');
		expect(opts.body).toMatchObject({
			relatedPersonId: 'p2',
			relationType: 'SPOUSE_OF',
			fromDate: '1923-05-12',
			fromDatePrecision: 'DAY',
			notes: 'Hochzeit'
		});
	});

	it('addRelationship omits precision when the date is empty (coherence)', async () => {
		const post = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: {} });
		vi.mocked(createApiClient).mockReturnValue({ POST: post } as unknown as ReturnType<
			typeof createApiClient
		>);
		const request = relForm({ fromDatePrecision: 'DAY' }); // precision but no date

		// eslint-disable-next-line @typescript-eslint/no-explicit-any
		await actions.addRelationship({ request, params: { id: 'p1' }, fetch: mockFetch } as any);

		const body = post.mock.calls[0][1].body;
		expect(body).not.toHaveProperty('fromDate');
		expect(body).not.toHaveProperty('fromDatePrecision');
	});

	it('updateRelationship PUTs to the relId path with the new body', async () => {
		const put = vi.fn().mockResolvedValue({ response: { ok: true, status: 200 }, data: {} });
		vi.mocked(createApiClient).mockReturnValue({ PUT: put } as unknown as ReturnType<
			typeof createApiClient
		>);
		const request = relForm({ relId: 'rel-9', fromDate: '1923-05-12', fromDatePrecision: 'DAY' });

		// eslint-disable-next-line @typescript-eslint/no-explicit-any
		await actions.updateRelationship({ request, params: { id: 'p1' }, fetch: mockFetch } as any);

		const [path, opts] = put.mock.calls[0];
		expect(path).toBe('/api/persons/{id}/relationships/{relId}');
		expect(opts.params.path).toMatchObject({ id: 'p1', relId: 'rel-9' });
		expect(opts.body).toMatchObject({
			relatedPersonId: 'p2',
			relationType: 'SPOUSE_OF',
			fromDate: '1923-05-12',
			fromDatePrecision: 'DAY'
		});
	});

	it('updateRelationship surfaces a backend error as a fail', async () => {
		const put = vi.fn().mockResolvedValue({
			response: { ok: false, status: 400 },
			error: { code: 'INVALID_RELATIONSHIP_DATES' }
		});
		vi.mocked(createApiClient).mockReturnValue({ PUT: put } as unknown as ReturnType<
			typeof createApiClient
		>);
		const request = relForm({ relId: 'rel-9' });

		const result = (await actions.updateRelationship({
			request,
			params: { id: 'p1' },
			fetch: mockFetch
			// eslint-disable-next-line @typescript-eslint/no-explicit-any
		} as any)) as { status: number; data: { relationshipError: string } };

		expect(result.status).toBe(400);
		expect(result.data.relationshipError).toBeTruthy();
	});
});
