import { beforeEach, describe, expect, it, vi } from 'vitest';
import { actions, load } from './+page.server';

const mockApi = {
	PUT: vi.fn(),
	POST: vi.fn(),
	DELETE: vi.fn()
};

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

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

// ─── load function ─────────────────────────────────────────────────────────────

describe('tags/[id] — load function', () => {
	it('returns mergeSuccess: true when url has ?merged param', async () => {
		const url = new URL('http://localhost/admin/tags/t1?merged=1');
		const result = await load({
			params: { id: 't1' },
			parent: async () => ({ tags: [{ id: 't1', name: 'Test', documentCount: 0 }] }),
			request: new Request('http://localhost/admin/tags/t1'),
			url
		} as never);
		expect((result as { mergeSuccess: boolean }).mergeSuccess).toBe(true);
	});

	it('returns mergeSuccess: false when url has no ?merged param', async () => {
		const url = new URL('http://localhost/admin/tags/t1');
		const result = await load({
			params: { id: 't1' },
			parent: async () => ({ tags: [{ id: 't1', name: 'Test', documentCount: 0 }] }),
			request: new Request('http://localhost/admin/tags/t1'),
			url
		} as never);
		expect((result as { mergeSuccess: boolean }).mergeSuccess).toBe(false);
	});
});

// ─── update action ─────────────────────────────────────────────────────────────

describe('tags/[id] — update action', () => {
	it('returns success: true when API responds ok', async () => {
		mockApi.PUT.mockResolvedValue({ response: { ok: true }, data: {} });

		const formData = new FormData();
		formData.set('name', 'Archiv');

		const result = await actions.update({
			params: { id: 't1' },
			request: { formData: async () => formData },
			fetch
		} as never);

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

	it('returns fail with error message when API responds not ok', async () => {
		mockApi.PUT.mockResolvedValue({
			response: { ok: false, status: 409 },
			error: { code: 'TAG_CONFLICT' }
		});

		const formData = new FormData();
		formData.set('name', 'Archiv');

		const result = await actions.update({
			params: { id: 't1' },
			request: { formData: async () => formData },
			fetch
		} as never);

		expect((result as { status: number }).status).toBe(409);
	});
});

// ─── merge action ─────────────────────────────────────────────────────────────

describe('tags/[id] — merge action', () => {
	it('redirects to target tag with ?merged=1 on successful merge', async () => {
		mockApi.POST.mockResolvedValue({
			response: { ok: true },
			data: { id: 't2', name: 'Reise' }
		});

		const formData = new FormData();
		formData.set('targetId', 't2');

		let redirectUrl: string | null = null;
		try {
			await actions.merge({
				params: { id: 't1' },
				request: { formData: async () => formData },
				fetch
			} as never);
		} catch (e: unknown) {
			const r = e as { location?: string };
			redirectUrl = r.location ?? null;
		}

		expect(redirectUrl).toBe('/admin/tags/t2?merged=1');
	});

	it('returns fail when merge API responds not ok', async () => {
		mockApi.POST.mockResolvedValue({
			response: { ok: false, status: 400 },
			error: { code: 'TAG_MERGE_SELF' }
		});

		const formData = new FormData();
		formData.set('targetId', 't1');

		const result = await actions.merge({
			params: { id: 't1' },
			request: { formData: async () => formData },
			fetch
		} as never);

		expect((result as { status: number }).status).toBe(400);
	});
});

// ─── delete action ─────────────────────────────────────────────────────────────

describe('tags/[id] — delete action (single)', () => {
	it('calls DELETE /api/tags/{id} when deleteMode=single', async () => {
		mockApi.DELETE.mockResolvedValue({ response: { ok: true } });

		const formData = new FormData();
		formData.set('deleteMode', 'single');

		try {
			await actions.delete({
				params: { id: 't1' },
				request: { formData: async () => formData },
				fetch
			} as never);
		} catch {
			// redirect expected
		}

		expect(mockApi.DELETE).toHaveBeenCalledWith(
			'/api/tags/{id}',
			expect.objectContaining({ params: { path: { id: 't1' } } })
		);
	});

	it('redirects to /admin/tags on successful single delete', async () => {
		mockApi.DELETE.mockResolvedValue({ response: { ok: true } });

		const formData = new FormData();
		formData.set('deleteMode', 'single');

		let redirectUrl: string | null = null;
		try {
			await actions.delete({
				params: { id: 't1' },
				request: { formData: async () => formData },
				fetch
			} as never);
		} catch (e: unknown) {
			const r = e as { location?: string };
			redirectUrl = r.location ?? null;
		}

		expect(redirectUrl).toBe('/admin/tags');
	});
});

describe('tags/[id] — delete action (subtree)', () => {
	it('calls DELETE /api/tags/{id}/subtree when deleteMode=subtree', async () => {
		mockApi.DELETE.mockResolvedValue({ response: { ok: true } });

		const formData = new FormData();
		formData.set('deleteMode', 'subtree');

		try {
			await actions.delete({
				params: { id: 't1' },
				request: { formData: async () => formData },
				fetch
			} as never);
		} catch {
			// redirect expected
		}

		expect(mockApi.DELETE).toHaveBeenCalledWith(
			'/api/tags/{id}/subtree',
			expect.objectContaining({ params: { path: { id: 't1' } } })
		);
	});

	it('returns fail with error message when delete API responds not ok', async () => {
		mockApi.DELETE.mockResolvedValue({
			response: { ok: false, status: 403 },
			error: { code: 'FORBIDDEN' }
		});

		const formData = new FormData();
		formData.set('deleteMode', 'single');

		const result = await actions.delete({
			params: { id: 't1' },
			request: { formData: async () => formData },
			fetch
		} as never);

		expect((result as { status: number }).status).toBe(403);
	});
});
