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

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

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

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

const writer = { groups: [{ permissions: ['READ_ALL', 'WRITE_ALL'] }] };
const reader = { groups: [{ permissions: ['READ_ALL'] }] };

/** Mock the typed client with a single response stubbed for every verb. */
function mockApi(response: { ok: boolean; status: number; error?: unknown }) {
	const result = {
		response: { ok: response.ok, status: response.status },
		error: response.error,
		data: response.ok ? {} : undefined
	};
	const apiCall = vi.fn(() => Promise.resolve(result));
	vi.mocked(createApiClient).mockReturnValue({
		GET: apiCall,
		PATCH: apiCall,
		POST: apiCall,
		PUT: apiCall,
		DELETE: apiCall
	} as unknown as ReturnType<typeof createApiClient>);
	return apiCall;
}

/** Build a SvelteKit RequestEvent with a FormData body and a user shape. */
function runAction(
	action: (typeof actions)[keyof typeof actions],
	formData: FormData,
	user: unknown
) {
	return action({
		request: new Request('http://localhost', { method: 'POST', body: formData }),
		fetch: vi.fn() as unknown as typeof fetch,
		locals: { user } as App.Locals
		// eslint-disable-next-line @typescript-eslint/no-explicit-any
	} as any);
}

describe('persons/review confirm action', () => {
	it('returns { success: true } on backend 200', async () => {
		const apiCall = mockApi({ ok: true, status: 200 });
		const fd = new FormData();
		fd.append('id', 'p-1');

		const result = await runAction(actions.confirm, fd, writer);

		expect(apiCall).toHaveBeenCalledOnce();
		expect(result).toEqual({ success: true });
	});

	it('returns fail(403) on backend 403', async () => {
		mockApi({ ok: false, status: 403, error: { code: 'FORBIDDEN' } });
		const fd = new FormData();
		fd.append('id', 'p-1');

		const result = await runAction(actions.confirm, fd, writer);

		expect(result).toMatchObject({ status: 403 });
	});

	it('returns fail(404) on backend 404', async () => {
		mockApi({ ok: false, status: 404, error: { code: 'NOT_FOUND' } });
		const fd = new FormData();
		fd.append('id', 'p-missing');

		const result = await runAction(actions.confirm, fd, writer);

		expect(result).toMatchObject({ status: 404 });
	});

	it('returns fail(403) when the user lacks WRITE_ALL (server-side guard)', async () => {
		const apiCall = mockApi({ ok: true, status: 200 });
		const fd = new FormData();
		fd.append('id', 'p-1');

		const result = await runAction(actions.confirm, fd, reader);

		expect(apiCall).not.toHaveBeenCalled();
		expect(result).toMatchObject({ status: 403 });
	});
});

describe('persons/review delete action', () => {
	it('returns { success: true } on backend 200', async () => {
		const apiCall = mockApi({ ok: true, status: 200 });
		const fd = new FormData();
		fd.append('id', 'p-1');

		const result = await runAction(actions.delete, fd, writer);

		expect(apiCall).toHaveBeenCalledOnce();
		expect(result).toEqual({ success: true });
	});

	it('returns fail(403) on backend 403', async () => {
		mockApi({ ok: false, status: 403, error: { code: 'FORBIDDEN' } });
		const fd = new FormData();
		fd.append('id', 'p-1');

		const result = await runAction(actions.delete, fd, writer);

		expect(result).toMatchObject({ status: 403 });
	});

	it('returns fail(404) on backend 404', async () => {
		mockApi({ ok: false, status: 404, error: { code: 'NOT_FOUND' } });
		const fd = new FormData();
		fd.append('id', 'p-missing');

		const result = await runAction(actions.delete, fd, writer);

		expect(result).toMatchObject({ status: 404 });
	});

	it('returns fail(403) when the user lacks WRITE_ALL (server-side guard)', async () => {
		const apiCall = mockApi({ ok: true, status: 200 });
		const fd = new FormData();
		fd.append('id', 'p-1');

		const result = await runAction(actions.delete, fd, reader);

		expect(apiCall).not.toHaveBeenCalled();
		expect(result).toMatchObject({ status: 403 });
	});
});

describe('persons/review merge action', () => {
	it('returns { success: true } on backend 200', async () => {
		const apiCall = mockApi({ ok: true, status: 200 });
		const fd = new FormData();
		fd.append('id', 'p-1');
		fd.append('targetPersonId', 'p-2');

		const result = await runAction(actions.merge, fd, writer);

		expect(apiCall).toHaveBeenCalledOnce();
		expect(result).toEqual({ success: true });
	});

	it('returns fail(400) when targetPersonId is missing', async () => {
		const apiCall = mockApi({ ok: true, status: 200 });
		const fd = new FormData();
		fd.append('id', 'p-1');

		const result = await runAction(actions.merge, fd, writer);

		expect(apiCall).not.toHaveBeenCalled();
		expect(result).toMatchObject({ status: 400 });
	});

	it('returns fail(403) on backend 403', async () => {
		mockApi({ ok: false, status: 403, error: { code: 'FORBIDDEN' } });
		const fd = new FormData();
		fd.append('id', 'p-1');
		fd.append('targetPersonId', 'p-2');

		const result = await runAction(actions.merge, fd, writer);

		expect(result).toMatchObject({ status: 403 });
	});

	it('returns fail(404) on backend 404', async () => {
		mockApi({ ok: false, status: 404, error: { code: 'NOT_FOUND' } });
		const fd = new FormData();
		fd.append('id', 'p-1');
		fd.append('targetPersonId', 'p-missing');

		const result = await runAction(actions.merge, fd, writer);

		expect(result).toMatchObject({ status: 404 });
	});

	it('returns fail(403) when the user lacks WRITE_ALL (server-side guard)', async () => {
		const apiCall = mockApi({ ok: true, status: 200 });
		const fd = new FormData();
		fd.append('id', 'p-1');
		fd.append('targetPersonId', 'p-2');

		const result = await runAction(actions.merge, fd, reader);

		expect(apiCall).not.toHaveBeenCalled();
		expect(result).toMatchObject({ status: 403 });
	});
});

describe('persons/review rename action', () => {
	it('returns { success: true } on backend 200', async () => {
		const apiCall = mockApi({ ok: true, status: 200 });
		const fd = new FormData();
		fd.append('id', 'p-1');
		fd.append('lastName', 'Smith');

		const result = await runAction(actions.rename, fd, writer);

		expect(apiCall).toHaveBeenCalledOnce();
		expect(result).toEqual({ success: true });
	});

	it('returns fail(400) when lastName is missing', async () => {
		const apiCall = mockApi({ ok: true, status: 200 });
		const fd = new FormData();
		fd.append('id', 'p-1');

		const result = await runAction(actions.rename, fd, writer);

		expect(apiCall).not.toHaveBeenCalled();
		expect(result).toMatchObject({ status: 400 });
	});

	it('returns fail(403) on backend 403', async () => {
		mockApi({ ok: false, status: 403, error: { code: 'FORBIDDEN' } });
		const fd = new FormData();
		fd.append('id', 'p-1');
		fd.append('lastName', 'Smith');

		const result = await runAction(actions.rename, fd, writer);

		expect(result).toMatchObject({ status: 403 });
	});

	it('returns fail(404) on backend 404', async () => {
		mockApi({ ok: false, status: 404, error: { code: 'NOT_FOUND' } });
		const fd = new FormData();
		fd.append('id', 'p-missing');
		fd.append('lastName', 'Smith');

		const result = await runAction(actions.rename, fd, writer);

		expect(result).toMatchObject({ status: 404 });
	});

	it('returns fail(403) when the user lacks WRITE_ALL (server-side guard)', async () => {
		const apiCall = mockApi({ ok: true, status: 200 });
		const fd = new FormData();
		fd.append('id', 'p-1');
		fd.append('lastName', 'Smith');

		const result = await runAction(actions.rename, fd, reader);

		expect(apiCall).not.toHaveBeenCalled();
		expect(result).toMatchObject({ status: 403 });
	});
});
