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;
};

/**
 * Role format returned by GET /users/me.
 *
 * Example:
 * roles: [
 *   {
 *     id: 6,
 *     name: "cdc_g-psf"
 *   }
 * ]
 */
export type CurrentUserRole = {
  id: number;
  name: string;
};

export type CurrentUserStakeholder = {
  id: number;
  name: string;
  logo: string | null;
  /*
   * 1 = Ministry, 2 = Private Sector working group.
   */
  stakeholderTypeId: number;
};

export type CurrentUser = {
  id: number;
  email: string;

  /*
   * Contact person name.
   */
  name: string | null;

  /*
   * Keep the old single-role field because some
   * existing components may still use it.
   */
  role?: string;

  /*
   * Full RBAC roles from GET /users/me.
   */
  roles: CurrentUserRole[];

  /*
   * Organization name / short name.
   */
  position: string | null;

  avatar: string | null;
  isActive: boolean;

  /*
   * The organisation(s) the user belongs to. `position` is only a label, so
   * this is what to read when a screen needs the stakeholder itself.
   */
  stakeholders?: { stakeholder: CurrentUserStakeholder }[];

  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"
).replace(/\/+$/, "");

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);
}

function normalizeCurrentUserRoles(
  value: unknown,
): CurrentUserRole[] {
  if (!Array.isArray(value)) {
    return [];
  }

  return value
    .map(
      (
        item,
      ): CurrentUserRole | null => {
        if (
          typeof item !== "object" ||
          item === null
        ) {
          return null;
        }

        const role = item as {
          id?: unknown;
          name?: unknown;

          role?: {
            id?: unknown;
            name?: unknown;
          };
        };

        /*
         * Direct API format:
         * roles: [{ id, name }]
         */
        if (
          typeof role.id === "number" &&
          typeof role.name === "string" &&
          role.name.trim()
        ) {
          return {
            id: role.id,
            name: role.name.trim(),
          };
        }

        /*
         * Prisma join-table format:
         * roles: [{ role: { id, name } }]
         */
        if (
          typeof role.role?.id ===
            "number" &&
          typeof role.role?.name ===
            "string" &&
          role.role.name.trim()
        ) {
          return {
            id: role.role.id,
            name: role.role.name.trim(),
          };
        }

        return null;
      },
    )
    .filter(
      (
        role,
      ): role is CurrentUserRole =>
        role !== null,
    );
}

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

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

/**
 * Fetch the authenticated user's profile
 * and 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 held by
 * the user, falling back to the first role.
 */
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");
}

/**
 * GET /api/v1/users/me
 *
 * Old authentication flow remains unchanged.
 * This only adds roles and isActive to the
 * normalized CurrentUser response.
 */
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.",
    );
  }

  const rawUser =
    response.data as CurrentUser & {
      roles?: unknown;
    };

  return {
    ...response.data,

    name:
      typeof response.data.name ===
      "string"
        ? response.data.name
        : null,

    position:
      typeof response.data.position ===
      "string"
        ? response.data.position
        : null,

    avatar: normalizeAvatarUrl(
      response.data.avatar ?? null,
    ),

    isActive:
      typeof response.data.isActive ===
      "boolean"
        ? response.data.isActive
        : true,

    roles: normalizeCurrentUserRoles(
      rawUser.roles,
    ),
  };
}

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),
      },
    );

  /*
   * Keep the old flow:
   * resolve application role from /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();
}