import { redirectToLoginAfterSessionExpired } from "@/features/auth/service/auth-service";

export type AccountProfile = {
    id: number;
    email: string;
    name: string;
    position: string | null;
    avatar: string | null;
    createdAt: string;
    updatedAt: string;
};

export type ApiResponse<T> = {
    success: boolean;
    statusCode: number;
    message: string;
    data: T;
    timestamp?: string;
    path?: string;
};

export type UpdateProfilePayload = {
    name: string;
    position?: string | null;
    avatar?: string | null;
    avatarFile?: File | null;
};

export type ChangePasswordPayload = {
    currentPassword: string;
    newPassword: string;
};

export type ChangeEmailPayload = {
    newEmail: string;
    currentPassword: string;
};

export type VerifyEmailOtpPayload = {
    otp: string;
};

type BackendAccountData =
    | Partial<AccountProfile>
    | {
        user?: Partial<AccountProfile> | null;
    }
    | null;

type ApiErrorResponse = {
    message?: string | string[];
};

const API_URL =
    process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001/api/v1";

function isRecord(value: unknown): value is Record<string, unknown> {
    return typeof value === "object" && value !== null;
}

function readString(value: unknown): string {
    return typeof value === "string" ? value : "";
}

function readNullableString(value: unknown): string | null {
    return typeof value === "string" && value.length > 0 ? value : null;
}

function getBackendAssetUrl(path: string): string {
    const backendUrl = API_URL.replace(/\/api(?:\/v\d+)?\/?$/, "");

    return `${backendUrl}${path}`;
}

function readAvatarUrl(value: unknown): string | null {
    const avatar = readNullableString(value);

    if (!avatar) {
        return null;
    }

    if (avatar.startsWith("/uploads/")) {
        return getBackendAssetUrl(avatar);
    }

    return avatar;
}

function readNumber(value: unknown): number {
    if (typeof value === "number" && Number.isFinite(value)) {
        return value;
    }

    if (typeof value === "string") {
        const numberValue = Number(value);

        if (Number.isFinite(numberValue)) {
            return numberValue;
        }
    }

    return 0;
}

function getMessage(message: string | string[] | undefined, fallback: string) {
    if (Array.isArray(message)) {
        return message[0] ?? fallback;
    }

    return message ?? fallback;
}

async function requestJson<T>(
    path: string,
    options: RequestInit = {},
): Promise<T> {
    const response = await fetch(`${API_URL}${path}`, {
        ...options,
        credentials: "include",
        headers: {
            "Content-Type": "application/json",
            ...options.headers,
        },
    });

    const data = (await response.json().catch(() => null)) as
        | T
        | ApiErrorResponse
        | null;

    if (!response.ok) {
        if (response.status === 401) {
            redirectToLoginAfterSessionExpired();
        }

        throw new Error(
            isRecord(data)
                ? getMessage(
                    data.message as string | string[] | undefined,
                    "Request failed.",
                )
                : "Request failed.",
        );
    }

    return data as T;
}

async function requestFormData<T>(path: string, formData: FormData): Promise<T> {
    const response = await fetch(`${API_URL}${path}`, {
        method: "PATCH",
        credentials: "include",
        body: formData,
    });

    const data = (await response.json().catch(() => null)) as
        | T
        | ApiErrorResponse
        | null;

    if (!response.ok) {
        if (response.status === 401) {
            redirectToLoginAfterSessionExpired();
        }

        throw new Error(
            isRecord(data)
                ? getMessage(
                    data.message as string | string[] | undefined,
                    "Request failed.",
                )
                : "Request failed.",
        );
    }

    return data as T;
}

export function extractAccountProfile(
    payload: unknown,
): Partial<AccountProfile> | null {
    if (!isRecord(payload)) {
        return null;
    }

    const source = isRecord(payload.user) ? payload.user : payload;

    return {
        id: readNumber(source.id),
        email: readString(source.email),
        name: readString(source.name),
        position: readNullableString(source.position),
        avatar: readAvatarUrl(source.avatar),
        createdAt: readString(source.createdAt),
        updatedAt: readString(source.updatedAt),
    };
}

function buildUpdateProfileBody(payload: UpdateProfilePayload): BodyInit {
    if (!payload.avatarFile) {
        const jsonPayload = {
            name: payload.name,
            ...(payload.position !== undefined
                ? { position: payload.position ?? "" }
                : {}),
            ...(payload.avatar ? { avatar: payload.avatar } : {}),
        };

        return JSON.stringify(jsonPayload);
    }

    const formData = new FormData();

    formData.append("name", payload.name);

    if (payload.position !== undefined && payload.position !== null) {
        formData.append("position", payload.position);
    }

    if (payload.avatar !== undefined && payload.avatar !== null) {
        formData.append("avatar", payload.avatar);
    }

    formData.append("avatarFile", payload.avatarFile);

    return formData;
}

export function mergeAccountProfile(
    profile: Partial<AccountProfile> | null,
    fallback?: AccountProfile | null,
): AccountProfile {
    return {
        id: profile?.id && profile.id > 0 ? profile.id : fallback?.id ?? 0,
        email: profile?.email || fallback?.email || "",
        name: profile?.name || fallback?.name || "",
        position:
            profile?.position !== undefined
                ? profile.position
                : fallback?.position ?? null,
        avatar:
            profile?.avatar !== undefined ? profile.avatar : fallback?.avatar ?? null,
        createdAt: profile?.createdAt || fallback?.createdAt || "",
        updatedAt: profile?.updatedAt || fallback?.updatedAt || "",
    };
}

export async function getMyProfile(): Promise<AccountProfile> {
    const response = await requestJson<ApiResponse<BackendAccountData>>(
        "/users/me",
        {
            method: "GET",
        },
    );

    const profile = extractAccountProfile(response.data);

    if (!profile) {
        throw new Error("Invalid profile response.");
    }

    return mergeAccountProfile(profile);
}

export async function updateMyProfile(
    payload: UpdateProfilePayload,
): Promise<Partial<AccountProfile> | null> {
    const body = buildUpdateProfileBody(payload);

    const response =
        body instanceof FormData
            ? await requestFormData<ApiResponse<BackendAccountData>>(
                "/users/me",
                body,
            )
            : await requestJson<ApiResponse<BackendAccountData>>(
                "/users/me",
                {
                    method: "PATCH",
                    body,
                },
            );

    return extractAccountProfile(response.data);
}

export async function changeMyPassword(
    payload: ChangePasswordPayload,
): Promise<ApiResponse<null>> {
    return requestJson<ApiResponse<null>>("/users/me/password", {
        method: "PATCH",
        body: JSON.stringify(payload),
    });
}

export async function changeMyEmail(
    payload: ChangeEmailPayload,
): Promise<ApiResponse<null>> {
    return requestJson<ApiResponse<null>>("/users/me/email-change", {
        method: "POST",
        body: JSON.stringify(payload),
    });
}

export async function verifyMyEmailOtp(
    payload: VerifyEmailOtpPayload,
): Promise<ApiResponse<BackendAccountData>> {
    return requestJson<ApiResponse<BackendAccountData>>(
        "/users/me/email-change/verify",
        {
            method: "POST",
            body: JSON.stringify(payload),
        },
    );
}
