import { command, form, getRequestEvent, query } from '$app/server';
import { error } from '@sveltejs/kit';
import {
	bookCoverUpload,
	booksUpload,
	bookQuerySchema,
	deleteBookFilesSchema,
	deleteBooksSchema,
	editBookMetadataSchema,
	updateBookProgressSchema,
	duplicateDismissalSchema,
	bookMergeSchema,
	type Book,
	type BooksUploadResult,
	type DuplicateBookGroup,
	bookFilesUpload
} from '$lib/schema/index';
import { stringCoerce, type PaginatedResponse } from '$lib/schema/common';
import { createQueryParams } from '$lib/utils';

/**
 * The backend's own message for a failed response, rather than its JSON envelope.
 *
 * A refused duplicate answers 409 with a `detail` worth reading and the offending
 * files in `extra`; passing the body through whole puts JSON in front of the reader.
 */
function detailOf(body: string): string {
	try {
		const parsed = JSON.parse(body);
		return typeof parsed?.detail === 'string' ? parsed.detail : body;
	} catch {
		return body;
	}
}

export const getBook = query(stringCoerce, async (id): Promise<Book> => {
	const { locals } = getRequestEvent();

	const response = await locals.api.get(`/books/${id}`);

	if (!response.ok) {
		if (response.status == 404) error(404, 'The book does not exist');
		error(500, 'An unkown error occurred');
	}

	return await response.json();
});

export const listBooks = query(bookQuerySchema, async (data): Promise<PaginatedResponse<Book>> => {
	const { locals } = getRequestEvent();

	const params = createQueryParams(data);

	const response = await locals.api.get(`/books?${params.toString()}`);

	if (!response.ok) error(500, 'An unkown error occurred');

	return await response.json();
});

export const updateBookMetadata = form(editBookMetadataSchema, async (data): Promise<Book> => {
	const { locals } = getRequestEvent();

	const response = await locals.api.patch(`/books/${data.book_id}`, data);

	if (!response.ok) {
		const message = await response.text();
		error(response.status, message);
	}

	return await response.json();
});

export const updateBookCover = form(bookCoverUpload, async ({ book_id, file }) => {
	const { locals } = getRequestEvent();

	const formData = new FormData();
	formData.append('file', file);

	const response = await locals.api.putMultipart(`/books/${book_id}/cover`, formData);

	if (!response.ok) {
		const message = await response.text();
		error(response.status, message);
	}

	return await response.json();
});

export const uploadBooks = form(
	booksUpload,
	async ({ library_id, files }): Promise<BooksUploadResult> => {
		const { locals } = getRequestEvent();

		const formData = new FormData();
		files.forEach((file) => {
			formData.append('files', file);
		});

		const response = await locals.api.postMultipart(
			`/books/fromFiles?library_id=${library_id}`,
			formData
		);

		if (!response.ok) {
			const message = await response.text();
			error(response.status, message);
		}

		return await response.json();
	}
);

export const uploadBookFiles = form(bookFilesUpload, async ({ book_id, files }) => {
	const { locals } = getRequestEvent();

	const formData = new FormData();
	files.forEach((file) => {
		formData.append('files', file);
	});

	const response = await locals.api.postMultipart(`/books/${book_id}/files`, formData);

	if (!response.ok) {
		// 409 here means the file is already stored under a different book, which is
		// something the reader can act on — so the message has to survive the trip.
		error(response.status, detailOf(await response.text()));
	}

	return await response.json();
});

export const deleteBooks = command(deleteBooksSchema, async (data) => {
	const { locals } = getRequestEvent();

	const params = createQueryParams(data);

	const response = await locals.api.delete(`/books?${params.toString()}`);

	if (!response.ok) {
		const message = await response.text();
		error(response.status, message);
	}
});

export const deleteBookFiles = command(deleteBookFilesSchema, async ({ book_id, ...data }) => {
	const { locals } = getRequestEvent();

	const params = createQueryParams(data);

	const response = await locals.api.delete(`/books/${book_id}/files?${params.toString()}`);

	if (!response.ok) {
		const message = await response.text();
		error(response.status, message);
	}
});

/**
 * Books already in the library that look like copies of one another.
 *
 * Metadata only, so every group is a question rather than a verdict — which is why
 * the screen it feeds offers a way to disagree.
 */
export const listDuplicateBooks = query(
	stringCoerce,
	async (libraryId): Promise<DuplicateBookGroup[]> => {
		const { locals } = getRequestEvent();

		const response = await locals.api.get(`/books/duplicate-books?library_id=${libraryId}`);

		if (!response.ok) error(response.status, detailOf(await response.text()));

		return await response.json();
	}
);

/**
 * Fold several books into one and delete the records folded in.
 *
 * Irreversible, so the caller is expected to have shown what is about to happen.
 */
export const mergeBooks = command(
	bookMergeSchema,
	async ({ library_id, ...data }): Promise<Book> => {
		const { locals } = getRequestEvent();

		const response = await locals.api.post(`/books/merge?library_id=${library_id}`, data);

		if (!response.ok) error(response.status, detailOf(await response.text()));

		return await response.json();
	}
);

/** Record that two books are not the same book, so the pair stops being proposed. */
export const dismissDuplicateBooks = command(duplicateDismissalSchema, async (data) => {
	const { locals } = getRequestEvent();

	const response = await locals.api.post('/books/duplicate-books/dismissals', data);

	if (!response.ok) error(response.status, detailOf(await response.text()));
});

/** Undo a dismissal, so the pair is proposed again. */
export const restoreDuplicateBooks = command(duplicateDismissalSchema, async (data) => {
	const { locals } = getRequestEvent();

	const params = createQueryParams(data);

	const response = await locals.api.delete(
		`/books/duplicate-books/dismissals?${params.toString()}`
	);

	if (!response.ok) error(response.status, detailOf(await response.text()));
});

export const updateBookProgress = command(
	updateBookProgressSchema,
	async ({ book_ids, ...data }) => {
		const { locals } = getRequestEvent();

		const params = createQueryParams({ book_ids: book_ids });

		const response = await locals.api.post(`/books/progress?${params.toString()}`, { ...data });

		if (!response.ok) {
			const message = await response.text();
			error(response.status, message);
		}
	}
);
