export type AdminUserStatus = "Active" | "Inactive";

export type ApiRole = {
  id: number;
  name: string;
};

export type ApiUser = {
  id: number | string;
  email?: string;
  name?: string;
  roles?: { id: number | string; name: string }[];
  position?: string | null;
  avatar?: string | null;
  avatarUrl?: string | null;
  profileImage?: string | null;
  isActive?: boolean;
  deletedAt?: string | null;
  status?: string | null;
  createdAt?: string;
  updatedAt?: string;
  lastLogin?: {
    ipAddress?: string | null;
    userAgent?: string | null;
    deviceType?: string | null;
    browser?: string | null;
    os?: string | null;
    createdAt?: string;
    loggedOutAt?: string | null;
  } | null;
};

export type LoginTrail = {
  id?: number | string;
  userId?: number | string;
  ipAddress?: string | null;
  userAgent?: string | null;
  deviceType?: string | null;
  browser?: string | null;
  os?: string | null;
  createdAt?: string;
  updatedAt?: string;
  loggedOutAt?: string | null;
};

export type UserListResponse =
  | ApiUser[]
  | {
      success?: boolean;
      message?: string;
      data?: ApiUser[] | { users?: ApiUser[]; items?: ApiUser[] };
      users?: ApiUser[];
      items?: ApiUser[];
    };

export type UserResponse =
  | ApiUser
  | {
      success?: boolean;
      message?: string;
      data?: ApiUser;
      user?: ApiUser;
    };

export type LoginTrailResponse =
  | LoginTrail[]
  | {
      success?: boolean;
      message?: string;
      data?: LoginTrail[] | { trails?: LoginTrail[] };
      trails?: LoginTrail[];
    };

export type CreateUserPayload = {
  email: string;
  password: string;
  name: string;
  roleIds: number[];
  position: string;
  avatarFile?: File | null;
};

export type UpdateUserPayload = {
  email?: string;
  password?: string;
  name?: string;
  roleIds?: number[];
  position?: string;
  avatarFile?: File | null;
  isActive?: boolean;
};

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

class ApiRequestError extends Error {
  status: number;
  body: unknown;

  constructor(message: string, status: number, body: unknown = null) {
    super(message);
    this.name = "ApiRequestError";
    this.status = status;
    this.body = body;
  }
}

function buildUrl(path: string) {
  const baseUrl = API_URL.replace(/\/$/, "");
  const cleanPath = path.startsWith("/") ? path : `/${path}`;

  return `${baseUrl}${cleanPath}`;
}

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

function parseResponseBody(text: string): unknown {
  if (!text) return null;

  try {
    return JSON.parse(text);
  } catch {
    return text;
  }
}

function getApiErrorMessage(body: unknown, status: number) {
  if (isRecord(body)) {
    const message = body.message;
    const error = body.error;
    const errors = body.errors;

    if (Array.isArray(message)) return message.join(" ");

    if (typeof message === "string" && message.trim()) return message;

    if (Array.isArray(errors)) return errors.join(" ");

    if (isRecord(errors)) return Object.values(errors).flat().join(" ");

    if (typeof error === "string" && error.trim()) return error;
  }

  if (status === 400) return "Bad Request";
  if (status === 401) return "Unauthorized";
  if (status === 403) return "Forbidden";
  if (status === 404) return "User not found";
  if (status === 405) return "Method Not Allowed";
  if (status === 409) return "Email already exists.";
  if (status === 500) return "Server Error";

  if (typeof body === "string" && body.trim()) return body;

  return `API Error: ${status}`;
}

async function apiJsonRequest<T = unknown>(
  path: string,
  options: RequestInit = {},
): Promise<T> {
  let res: Response;

  try {
    res = await fetch(buildUrl(path), {
      ...options,
      credentials: "include",
      headers: {
        "Content-Type": "application/json",
        ...options.headers,
      },
    });
  } catch (error) {
    const message =
      error instanceof Error ? error.message : "Network request failed";

    throw new ApiRequestError(message, 0, null);
  }

  const responseText = await res.text();
  const responseBody = parseResponseBody(responseText);

  if (!res.ok) {
    throw new ApiRequestError(
      getApiErrorMessage(responseBody, res.status),
      res.status,
      responseBody,
    );
  }

  return responseBody as T;
}

async function apiFormRequest<T = unknown>(
  path: string,
  method: "POST" | "PATCH",
  formData: FormData,
): Promise<T> {
  let res: Response;

  try {
    res = await fetch(buildUrl(path), {
      method,
      credentials: "include",
      body: formData,
    });
  } catch (error) {
    const message =
      error instanceof Error ? error.message : "Network request failed";

    throw new ApiRequestError(message, 0, null);
  }

  const responseText = await res.text();
  const responseBody = parseResponseBody(responseText);

  if (!res.ok) {
    throw new ApiRequestError(
      getApiErrorMessage(responseBody, res.status),
      res.status,
      responseBody,
    );
  }

  return responseBody as T;
}

function hasFile(value: unknown): value is File {
  return typeof File !== "undefined" && value instanceof File;
}

function appendText(formData: FormData, key: string, value: unknown) {
  if (value === undefined || value === null || value === "") return;

  formData.append(key, String(value));
}

function buildUserFormData(payload: CreateUserPayload | UpdateUserPayload) {
  const formData = new FormData();

  appendText(formData, "name", payload.name);
  appendText(formData, "email", payload.email);
  appendText(formData, "password", payload.password);
  appendText(formData, "position", payload.position);

  if (Array.isArray(payload.roleIds)) {
    // Sent as a JSON string; the backend DTO parses it back into number[].
    formData.append("roleIds", JSON.stringify(payload.roleIds));
  }

  if ("isActive" in payload && typeof payload.isActive === "boolean") {
    formData.append("isActive", String(payload.isActive));
  }

  if (hasFile(payload.avatarFile)) {
    formData.append("avatarFile", payload.avatarFile);
  }

  return formData;
}

function extractUsers(payload: UserListResponse): ApiUser[] {
  if (Array.isArray(payload)) return payload;

  if (!isRecord(payload)) return [];

  if ("data" in payload) {
    const data = payload.data;

    if (Array.isArray(data)) return data as ApiUser[];

    if (isRecord(data)) {
      if ("users" in data && Array.isArray(data.users)) {
        return data.users as ApiUser[];
      }

      if ("items" in data && Array.isArray(data.items)) {
        return data.items as ApiUser[];
      }
    }
  }

  if ("users" in payload && Array.isArray(payload.users)) {
    return payload.users as ApiUser[];
  }

  if ("items" in payload && Array.isArray(payload.items)) {
    return payload.items as ApiUser[];
  }

  return [];
}

function extractRoleList(payload: unknown): ApiRole[] {
  const records = Array.isArray(payload)
    ? payload
    : isRecord(payload) && Array.isArray(payload.data)
      ? payload.data
      : [];

  return (records as unknown[])
    .filter(isRecord)
    .map((role) => ({ id: Number(role.id), name: String(role.name ?? "") }))
    .filter((role) => Number.isFinite(role.id) && role.name.length > 0);
}

function extractUser(payload: UserResponse): ApiUser {
  const root: unknown = payload;

  // Unwrap the API envelope { data: ... } if present.
  const container = isRecord(root) && isRecord(root.data) ? root.data : root;

  // Controllers return { message, user }, so unwrap a nested { user: ... } too.
  if (isRecord(container) && isRecord(container.user)) {
    return container.user as ApiUser;
  }

  return container as ApiUser;
}

function extractTrails(payload: LoginTrailResponse): LoginTrail[] {
  if (Array.isArray(payload)) return payload;

  if (!isRecord(payload)) return [];

  if ("trails" in payload && Array.isArray(payload.trails)) {
    return payload.trails as LoginTrail[];
  }

  if ("data" in payload) {
    const data = payload.data;

    if (Array.isArray(data)) return data as LoginTrail[];

    if (isRecord(data) && "trails" in data && Array.isArray(data.trails)) {
      return data.trails as LoginTrail[];
    }
  }

  return [];
}

export const userManagementService = {
  async getUsers(): Promise<ApiUser[]> {
    const payload = await apiJsonRequest<UserListResponse>("/users", {
      method: "GET",
    });

    return extractUsers(payload);
  },

  async getRoles(): Promise<ApiRole[]> {
    const payload = await apiJsonRequest<unknown>("/roles", {
      method: "GET",
    });

    return extractRoleList(payload);
  },

  async getUserLoginTrails(id: string | number): Promise<LoginTrail[]> {
    const payload = await apiJsonRequest<LoginTrailResponse>(
      `/users/${id}/login-trails`,
      {
        method: "GET",
      },
    );

    return extractTrails(payload);
  },

  async createUser(payload: CreateUserPayload): Promise<ApiUser> {
    if (hasFile(payload.avatarFile)) {
      const formData = buildUserFormData(payload);

      const response = await apiFormRequest<UserResponse>(
        "/users",
        "POST",
        formData,
      );

      return extractUser(response);
    }

    const { avatarFile, ...jsonPayload } = payload;

    const response = await apiJsonRequest<UserResponse>("/users", {
      method: "POST",
      body: JSON.stringify(jsonPayload),
    });

    return extractUser(response);
  },

  async updateUser(
    id: string | number,
    payload: UpdateUserPayload,
  ): Promise<ApiUser> {
    if (hasFile(payload.avatarFile)) {
      const formData = buildUserFormData(payload);

      const response = await apiFormRequest<UserResponse>(
        `/users/${id}`,
        "PATCH",
        formData,
      );

      return extractUser(response);
    }

    const { avatarFile, ...jsonPayload } = payload;

    const response = await apiJsonRequest<UserResponse>(`/users/${id}`, {
      method: "PATCH",
      body: JSON.stringify(jsonPayload),
    });

    return extractUser(response);
  },

  async activateUser(id: string | number): Promise<void> {
    await apiJsonRequest(`/users/${id}`, {
      method: "PATCH",
      body: JSON.stringify({
        isActive: true,
      }),
    });
  },

  async deactivateUser(id: string | number): Promise<void> {
    await apiJsonRequest(`/users/${id}`, {
      method: "PATCH",
      body: JSON.stringify({
        isActive: false,
      }),
    });
  },

  async deleteUser(id: string | number): Promise<void> {
    await apiJsonRequest(`/users/${id}`, {
      method: "DELETE",
    });
  },
};