import {
  AUTH_COOKIE_MAX_AGE_SECONDS,
  AUTH_COOKIE_NAME,
  AUTH_EMAIL_COOKIE_NAME,
  AUTH_ROLE_COOKIE_NAME,
  canRoleUseApp,
  getRoleHomePath,
  isUserRole,
  rolePriority,
  rolesMatch,
  type AuthPermission,
  type UserRole,
} from "@/features/auth/auth-data";

type MessageValue = string | string[] | undefined;

type BackendResponse<T> = {
  success?: boolean;
  message?: MessageValue;
  data?: T | null;
};

type LoginBackendData = {
  role?: string;
  user?: {
    email?: string;
    role?: string;
  };
};

export type LoginPayload = {
  email: string;
  password: string;
};

export type LoginResponse = {
  success: boolean;
  message: string;
  redirectTo?: string;
};

export type ForgotPasswordPayload = {
  email: string;
};

export type ResetPasswordPayload = {
  email: string;
  otp: string;
  newPassword: string;
};

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

export type AuthSession = {
  permissions: AuthPermission[];
  role: UserRole | null;
};

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

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

  return message ?? fallback;
}

async function requestNest<T>(
  path: string,
  options: RequestInit = {},
): Promise<BackendResponse<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
    | BackendResponse<T>
    | null;

  if (!response.ok) {
    if (response.status === 401 && path !== "/auth/login") {
      redirectToLoginAfterSessionExpired();
    }

    throw new Error(getMessage(data?.message, "Request failed."));
  }

  return data ?? {};
}

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

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

function normalizeAvatarUrl(avatar: string | null) {
  if (!avatar || !avatar.startsWith("/uploads/")) {
    return avatar;
  }

  return getBackendAssetUrl(avatar);
}

type AuthMeRole = { id: number; name: string };

type AuthMeUser = {
  email?: string;
  permissions?: AuthPermission[];
  roles?: AuthMeRole[];
};

// Fetch the authenticated user's profile + RBAC roles from the backend.
async function fetchAuthMe(): Promise<AuthMeUser | null> {
  const response = await requestNest<{ user?: AuthMeUser }>("/auth/me", {
    method: "GET",
  }).catch(() => null);

  return response?.data?.user ?? null;
}

// Pick the highest-priority role the user holds, falling back to their first
// role. Backend role names (e.g. "Administrator", "PSWG") are matched
// case-insensitively; the user's actual role name is what gets returned/stored.
function resolveAppRole(roles: AuthMeRole[] | undefined): UserRole | null {
  const names = (roles ?? []).map((item) => item.name).filter(isUserRole);

  if (names.length === 0) {
    return null;
  }

  const prioritized = rolePriority.find((role) =>
    names.some((name) => rolesMatch(name, role)),
  );

  if (prioritized) {
    return names.find((name) => rolesMatch(name, prioritized)) ?? prioritized;
  }

  return names[0];
}

function setClientCookie(name: string, value: string) {
  if (typeof document === "undefined") {
    return;
  }

  const secure = window.location.protocol === "https:" ? "; Secure" : "";

  document.cookie = `${name}=${encodeURIComponent(
    value,
  )}; Max-Age=${AUTH_COOKIE_MAX_AGE_SECONDS}; Path=/; SameSite=Lax${secure}`;
}

function clearClientCookie(name: string) {
  if (typeof document === "undefined") {
    return;
  }

  document.cookie = `${name}=; Max-Age=0; Path=/; SameSite=Lax`;
}

function getClientCookie(name: string) {
  if (typeof document === "undefined") {
    return undefined;
  }

  const cookie = document.cookie
    .split("; ")
    .find((item) => item.startsWith(`${name}=`));

  return cookie
    ? decodeURIComponent(cookie.split("=").slice(1).join("="))
    : undefined;
}

function setLoginCookies(email: string, role: UserRole) {
  setClientCookie(AUTH_COOKIE_NAME, "true");
  setClientCookie(AUTH_EMAIL_COOKIE_NAME, email);
  setClientCookie(AUTH_ROLE_COOKIE_NAME, role);
}

export function getClientAuthRole(): UserRole | null {
  const role = getClientCookie(AUTH_ROLE_COOKIE_NAME);

  if (!isUserRole(role) || !canRoleUseApp(role)) {
    return null;
  }

  return role;
}

function clearLoginCookies() {
  clearClientCookie(AUTH_COOKIE_NAME);
  clearClientCookie(AUTH_EMAIL_COOKIE_NAME);
  clearClientCookie(AUTH_ROLE_COOKIE_NAME);
}

export function redirectToLoginAfterSessionExpired() {
  if (typeof window === "undefined") {
    return;
  }

  clearLoginCookies();

  if (window.location.pathname.startsWith("/auth")) {
    return;
  }

  window.location.assign("/auth/login");
}

export async function getCurrentUser(): Promise<CurrentUser> {
  const response = await requestNest<CurrentUser>("/users/me", {
    method: "GET",
  });

  if (!response.data) {
    throw new Error("Unable to load the current user.");
  }

  return {
    ...response.data,
    avatar: normalizeAvatarUrl(response.data.avatar),
  };
}

export async function getAuthSession(): Promise<AuthSession> {
  const me = await fetchAuthMe();
  const role = resolveAppRole(me?.roles);

  return {
    permissions: me?.permissions ?? [],
    role: role && canRoleUseApp(role) ? role : null,
  };
}

export async function login(payload: LoginPayload): Promise<LoginResponse> {
  const response = await requestNest<LoginBackendData>("/auth/login", {
    method: "POST",
    body: JSON.stringify(payload),
  });

  // Resolve the app role from the RBAC roles returned by /auth/me.
  const me = await fetchAuthMe();
  const role = resolveAppRole(me?.roles);

  if (!role) {
    await logout();
    throw new Error("Login succeeded, but this account has no usable role.");
  }

  if (!canRoleUseApp(role)) {
    await logout();
    throw new Error("Your account role cannot access this dashboard.");
  }

  const email = me?.email ?? payload.email;
  setLoginCookies(email, role);

  return {
    success: true,
    message: getMessage(response.message, "Logged in successfully."),
    redirectTo: getRoleHomePath(role) ?? undefined,
  };
}

export async function forgotPassword(payload: ForgotPasswordPayload) {
  const response = await requestNest<null>("/auth/forgot-password", {
    method: "POST",
    body: JSON.stringify(payload),
  });

  return {
    message: getMessage(
      response.message,
      "If this email exists, a password reset OTP has been sent.",
    ),
  };
}

export async function resetPassword(payload: ResetPasswordPayload) {
  const response = await requestNest<null>("/auth/reset-password", {
    method: "POST",
    body: JSON.stringify(payload),
  });

  return {
    message: getMessage(response.message, "Password has been reset successfully."),
  };
}

export async function logout() {
  await requestNest<null>("/auth/logout", {
    method: "POST",
  }).catch(() => undefined);

  clearLoginCookies();
}
