/*
 * Copyright (C) 2026 Fluxer Contributors
 *
 * This file is part of Fluxer.
 *
 * Fluxer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Fluxer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
 */

import type {ApiTestHarness} from '@fluxer/api/src/test/ApiTestHarness';
import {createBuilder, createBuilderWithoutAuth} from '@fluxer/api/src/test/TestRequestBuilder';
import {UserFlags} from '@fluxer/constants/src/UserConstants';
import type {
	PushSubscribeResponse,
	PushSubscriptionsListResponse,
	UserPartialResponse,
	UserPrivateResponse,
	UserProfileFullResponse,
	UserSettingsResponse,
	UserTagCheckResponse,
} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
import {expect} from 'vitest';

export async function fetchUserMe(
	harness: ApiTestHarness,
	token: string,
): Promise<{response: Response; json: UserPrivateResponse}> {
	const {response, json} = await createBuilder<UserPrivateResponse>(harness, token)
		.get('/users/@me')
		.executeWithResponse();

	if (response.status !== 200) {
		throw new Error(`Expected 200, got ${response.status}`);
	}
	return {response, json};
}

export async function fetchUser(
	harness: ApiTestHarness,
	userId: string,
	token: string,
): Promise<{response: Response; json: UserPartialResponse}> {
	const {response, json} = await createBuilder<UserPartialResponse>(harness, token)
		.get(`/users/${userId}`)
		.executeWithResponse();

	if (response.status !== 200) {
		throw new Error(`Expected 200, got ${response.status}`);
	}
	return {response, json};
}

export async function fetchUserProfile(
	harness: ApiTestHarness,
	targetId: string,
	token: string,
): Promise<{response: Response; json: UserProfileFullResponse}> {
	const {response, json} = await createBuilder<UserProfileFullResponse>(harness, token)
		.get(`/users/${targetId}/profile`)
		.executeWithResponse();

	if (response.status !== 200) {
		throw new Error(`Expected 200, got ${response.status}`);
	}
	return {response, json};
}

export async function updateUserProfile(
	harness: ApiTestHarness,
	token: string,
	data: {
		username?: string;
		discriminator?: string;
		global_name?: string;
		bio?: string;
		password?: string;
	},
): Promise<{response: Response; json: UserPrivateResponse}> {
	const {response, json} = await createBuilder<UserPrivateResponse>(harness, token)
		.patch('/users/@me')
		.body(data)
		.executeWithResponse();

	if (response.status !== 200) {
		throw new Error(`Expected 200, got ${response.status}`);
	}
	return {response, json};
}

export async function updateUserSettings(
	harness: ApiTestHarness,
	token: string,
	data: Record<string, unknown>,
): Promise<{response: Response; json: UserSettingsResponse}> {
	const {response, json} = await createBuilder<UserSettingsResponse>(harness, token)
		.patch('/users/@me/settings')
		.body(data)
		.executeWithResponse();

	if (response.status !== 200) {
		throw new Error(`Expected 200, got ${response.status}`);
	}
	return {response, json};
}

export async function fetchUserSettings(
	harness: ApiTestHarness,
	token: string,
): Promise<{response: Response; json: UserSettingsResponse}> {
	const {response, json} = await createBuilder<UserSettingsResponse>(harness, token)
		.get('/users/@me/settings')
		.executeWithResponse();

	if (response.status !== 200) {
		throw new Error(`Expected 200, got ${response.status}`);
	}
	return {response, json};
}

export async function checkUsernameDiscriminatorAvailability(
	harness: ApiTestHarness,
	username: string,
	discriminator: string,
	token: string,
): Promise<{response: Response; json: UserTagCheckResponse}> {
	const {response, json} = await createBuilder<UserTagCheckResponse>(harness, token)
		.get(`/users/check-tag?username=${encodeURIComponent(username)}&discriminator=${encodeURIComponent(discriminator)}`)
		.executeWithResponse();

	if (response.status !== 200) {
		throw new Error(`Expected 200, got ${response.status}`);
	}
	return {response, json};
}

export async function disableAccount(harness: ApiTestHarness, token: string, password: string): Promise<void> {
	await createBuilder<void>(harness, token).post('/users/@me/disable').body({password}).expect(204).execute();
}

export async function deleteAccount(harness: ApiTestHarness, token: string, password: string): Promise<void> {
	await createBuilder<void>(harness, token).post('/users/@me/delete').body({password}).expect(204).execute();
}

export async function setUserNote(
	harness: ApiTestHarness,
	token: string,
	targetId: string,
	note: string | null,
): Promise<void> {
	await createBuilder<void>(harness, token).put(`/users/@me/notes/${targetId}`).body({note}).expect(204).execute();
}

export async function fetchUserNote(
	harness: ApiTestHarness,
	token: string,
	targetId: string,
): Promise<{response: Response; json: unknown}> {
	const {response, json} = await createBuilder<unknown>(harness, token)
		.get(`/users/@me/notes/${targetId}`)
		.executeWithResponse();

	if (response.status !== 200) {
		throw new Error(`Expected 200, got ${response.status}`);
	}
	return {response, json};
}

export async function preloadMessages(
	harness: ApiTestHarness,
	token: string,
	channels: Array<number>,
): Promise<{response: Response; json: Record<string, never>}> {
	const {response, json} = await createBuilder<Record<string, never>>(harness, token)
		.post('/users/@me/preload-messages')
		.body({channels})
		.executeWithResponse();

	if (response.status !== 200) {
		throw new Error(`Expected 200, got ${response.status}`);
	}
	return {response, json: json as Record<string, never>};
}

export async function requestBulkMessageDelete(
	harness: ApiTestHarness,
	token: string,
	password: string,
): Promise<void> {
	await createBuilder<void>(harness, token).post('/users/@me/messages/delete').body({password}).expect(204).execute();
}

export async function cancelBulkMessageDelete(harness: ApiTestHarness, token: string): Promise<void> {
	await createBuilder<void>(harness, token).delete('/users/@me/messages/delete').expect(200).execute();
}

export async function updateGuildSettings(
	harness: ApiTestHarness,
	token: string,
	data: Record<string, unknown>,
): Promise<{response: Response; json: unknown}> {
	const {response, json} = await createBuilder<unknown>(harness, token)
		.patch('/users/@me/guilds/@me/settings')
		.body(data)
		.executeWithResponse();

	if (response.status !== 200) {
		throw new Error(`Expected 200, got ${response.status}`);
	}
	return {response, json};
}

export async function expectDataExists(
	harness: ApiTestHarness,
	userId: string,
): Promise<{
	hasSelfDeletedFlag: boolean;
	hasDeletedFlag: boolean;
	userExists: boolean;
	pendingDeletionAt: string | null;
	relationshipsCount: number;
	emailCleared: boolean;
	passwordCleared: boolean;
}> {
	const data = await createBuilderWithoutAuth<{
		user_exists: boolean;
		has_self_deleted_flag: boolean;
		has_deleted_flag: boolean;
		pending_deletion_at: string | null;
		relationships_count: number;
		email_cleared: boolean;
		password_cleared: boolean;
	}>(harness)
		.get(`/test/users/${userId}/data-exists`)
		.execute();

	return {
		hasSelfDeletedFlag: data.has_self_deleted_flag,
		hasDeletedFlag: data.has_deleted_flag,
		userExists: data.user_exists,
		pendingDeletionAt: data.pending_deletion_at,
		relationshipsCount: data.relationships_count,
		emailCleared: data.email_cleared,
		passwordCleared: data.password_cleared,
	};
}

export async function triggerDeletionWorker(harness: ApiTestHarness): Promise<{scheduled: number}> {
	return createBuilderWithoutAuth<{scheduled: number}>(harness)
		.post('/test/worker/process-pending-deletions')
		.execute();
}

export async function setPendingDeletionAt(harness: ApiTestHarness, userId: string, when: Date): Promise<void> {
	await createBuilderWithoutAuth<void>(harness)
		.post(`/test/users/${userId}/set-pending-deletion`)
		.body({
			pending_deletion_at: when.toISOString(),
		})
		.execute();
}

export async function waitForDeletionCompletion(
	harness: ApiTestHarness,
	userId: string,
	timeoutMs = 60000,
): Promise<void> {
	const start = Date.now();
	let backoff = 100;
	const maxBackoff = 5000;

	while (Date.now() - start < timeoutMs) {
		const data = await createBuilderWithoutAuth<{
			user_exists: boolean;
			has_deleted_flag: boolean;
		}>(harness)
			.get(`/test/users/${userId}/data-exists`)
			.execute();

		if (data.user_exists && data.has_deleted_flag) {
			return;
		}

		await new Promise((resolve) => setTimeout(resolve, backoff));
		backoff = Math.min(backoff * 1.5, maxBackoff);
	}

	throw new Error('Deletion did not complete within timeout');
}

export function assertUserDisabled(user: UserPrivateResponse): void {
	expect(user.flags & Number(UserFlags.DISABLED)).not.toBe(0);
}

export function assertUserNotDisabled(user: UserPrivateResponse): void {
	expect(user.flags & Number(UserFlags.DISABLED)).toBe(0);
}

export interface UserProfileUpdateResult {
	id: string;
	username: string;
	avatar: string | null;
	banner: string | null;
}

export async function updateAvatar(
	harness: ApiTestHarness,
	token: string,
	avatarDataUrl: string | null,
): Promise<UserProfileUpdateResult> {
	return createBuilder<UserProfileUpdateResult>(harness, token)
		.patch('/users/@me')
		.body({avatar: avatarDataUrl})
		.execute();
}

export async function updateBanner(
	harness: ApiTestHarness,
	token: string,
	bannerDataUrl: string | null,
): Promise<UserProfileUpdateResult> {
	return createBuilder<UserProfileUpdateResult>(harness, token)
		.patch('/users/@me')
		.body({banner: bannerDataUrl})
		.execute();
}

export interface VerifyAssetResponse {
	hash: string | null;
	exists_in_s3: boolean | null;
	s3_path: string | null;
}

export async function verifyUserAvatarInS3(harness: ApiTestHarness, userId: string): Promise<VerifyAssetResponse> {
	return createBuilderWithoutAuth<VerifyAssetResponse>(harness).get(`/test/users/${userId}/verify-avatar`).execute();
}

export async function verifyUserBannerInS3(harness: ApiTestHarness, userId: string): Promise<VerifyAssetResponse> {
	return createBuilderWithoutAuth<VerifyAssetResponse>(harness).get(`/test/users/${userId}/verify-banner`).execute();
}

export async function grantPremium(harness: ApiTestHarness, userId: string, premiumType: number): Promise<void> {
	await createBuilderWithoutAuth(harness)
		.post(`/test/users/${userId}/premium`)
		.body({premium_type: premiumType})
		.execute();
}

export async function subscribePush(
	harness: ApiTestHarness,
	token: string,
	endpoint: string,
	options?: {p256dh?: string; auth?: string; userAgent?: string},
): Promise<PushSubscribeResponse> {
	return createBuilder<PushSubscribeResponse>(harness, token)
		.post('/users/@me/push/subscribe')
		.body({
			endpoint,
			keys: {
				p256dh: options?.p256dh ?? 'test-p256dh-key',
				auth: options?.auth ?? 'test-auth-key',
			},
			user_agent: options?.userAgent,
		})
		.execute();
}

export async function listPushSubscriptions(
	harness: ApiTestHarness,
	token: string,
): Promise<PushSubscriptionsListResponse> {
	return createBuilder<PushSubscriptionsListResponse>(harness, token)
		.get('/users/@me/push/subscriptions')
		.execute();
}

export async function deletePushSubscription(
	harness: ApiTestHarness,
	token: string,
	subscriptionId: string,
): Promise<void> {
	await createBuilder<void>(harness, token)
		.delete(`/users/@me/push/subscriptions/${subscriptionId}`)
		.execute();
}
