/**
 * Standardized Error Handling for Portal
 * Provides consistent error handling and user-friendly messages
 */

import { ApiError as ClientApiError } from "@/lib/api";
import { apiErrorResponseSchema, type ApiErrorResponse } from "@customer-portal/domain/common";

export type ApiErrorPayload = ApiErrorResponse;

export interface ApiErrorInfo {
  code: string;
  message: string;
  shouldLogout?: boolean;
  shouldRetry?: boolean;
}

/**
 * Extract error information from various error types
 */
export function getErrorInfo(error: unknown): ApiErrorInfo {
  if (error instanceof ClientApiError) {
    const info = parseClientApiError(error);
    if (info) {
      return info;
    }
  }

  // Handle fetch/network errors
  if (error instanceof Error) {
    if (error.name === "TypeError" && error.message.includes("fetch")) {
      return {
        code: "NETWORK_ERROR",
        message:
          "Unable to connect to the server. Please check your internet connection and try again.",
        shouldRetry: true,
      };
    }

    if (error.name === "AbortError") {
      return {
        code: "REQUEST_TIMEOUT",
        message: "The request timed out. Please try again.",
        shouldRetry: true,
      };
    }

    return {
      code: "UNKNOWN_ERROR",
      message: "An unexpected error occurred. Please try again.",
      shouldRetry: true,
    };
  }

  // Fallback for unknown error types
  return {
    code: "UNKNOWN_ERROR",
    message: "An unexpected error occurred. Please try again.",
    shouldRetry: true,
  };
}

export function isApiErrorPayload(error: unknown): error is ApiErrorPayload {
  const parsed = apiErrorResponseSchema.safeParse(error);
  return parsed.success;
}

/**
 * Determine if the user should be logged out for this error
 */
function shouldLogoutForError(code: string): boolean {
  const logoutCodes = ["TOKEN_REVOKED", "INVALID_REFRESH_TOKEN"];
  return logoutCodes.includes(code);
}

/**
 * Determine if the request should be retried for this error
 */
function shouldRetryForError(code: string): boolean {
  const noRetryCodes = [
    "INVALID_CREDENTIALS",
    "FORBIDDEN",
    "ADMIN_REQUIRED",
    "ACCOUNT_LOCKED",
    "VALIDATION_ERROR",
    "ACCOUNT_ALREADY_LINKED",
    "ACCOUNT_EXISTS",
    "CUSTOMER_NOT_FOUND",
    "INVALID_REQUEST",
  ];
  return !noRetryCodes.includes(code);
}

/**
 * Get a user-friendly error message for display in UI
 */
export function getUserFriendlyMessage(error: unknown): string {
  const errorInfo = getErrorInfo(error);
  return errorInfo.message;
}

/**
 * Handle authentication errors consistently
 */
export function handleAuthError(error: unknown, logout: () => void | Promise<void>): boolean {
  const errorInfo = getErrorInfo(error);

  if (errorInfo.shouldLogout) {
    void logout();
    return true;
  }

  return false;
}

/**
 * Create a standardized error for logging
 */
export function createErrorLog(
  error: unknown,
  context: string
): {
  context: string;
  code: string;
  message: string;
  timestamp: string;
} {
  const errorInfo = getErrorInfo(error);

  return {
    context,
    code: errorInfo.code,
    message: errorInfo.message,
    timestamp: new Date().toISOString(),
  };
}

function parseClientApiError(error: ClientApiError): ApiErrorInfo | null {
  const status = error.response?.status;
  const parsedBody = parseRawErrorBody(error.body);

  const payloadInfo = parsedBody ? deriveInfoFromPayload(parsedBody, status) : null;

  if (payloadInfo) {
    return payloadInfo;
  }

  return {
    code: status ? httpStatusCodeToLabel(status) : "API_ERROR",
    message: error.message,
    shouldLogout: status ? shouldLogoutForError(httpStatusCodeToLabel(status)) : false,
    shouldRetry: typeof status === "number" ? status >= 500 : true,
  };
}

function parseRawErrorBody(body: unknown): unknown {
  if (!body) {
    return null;
  }

  if (typeof body === "string") {
    try {
      return JSON.parse(body);
    } catch {
      return body;
    }
  }

  return body;
}

function deriveInfoFromPayload(payload: unknown, status?: number): ApiErrorInfo | null {
  const parsed = apiErrorResponseSchema.safeParse(payload);
  if (parsed.success) {
    const code = parsed.data.error.code;
    return {
      code,
      message: parsed.data.error.message,
      shouldLogout: shouldLogoutForError(code),
      shouldRetry: shouldRetryForError(code),
    };
  }

  if (isGlobalErrorPayload(payload)) {
    const code = payload.code || payload.error || httpStatusCodeToLabel(status);
    const message = payload.message || "Request failed. Please try again.";
    const derivedStatus = payload.statusCode ?? status;

    return {
      code,
      message,
      shouldLogout: shouldLogoutForError(code),
      shouldRetry:
        typeof derivedStatus === "number" ? derivedStatus >= 500 : shouldRetryForError(code),
    };
  }

  if (
    typeof payload === "object" &&
    payload !== null &&
    "message" in payload &&
    typeof (payload as { message?: unknown }).message === "string"
  ) {
    const payloadWithMessage = payload as { code?: unknown; message: string };
    const candidateCode = payloadWithMessage.code;
    const code = typeof candidateCode === "string" ? candidateCode : httpStatusCodeToLabel(status);

    return {
      code,
      message: payloadWithMessage.message,
      shouldLogout: shouldLogoutForError(code),
      shouldRetry: typeof status === "number" ? status >= 500 : shouldRetryForError(code),
    };
  }

  return null;
}

function isGlobalErrorPayload(payload: unknown): payload is {
  success: false;
  code?: string;
  message?: string;
  error?: string;
  statusCode?: number;
} {
  return (
    typeof payload === "object" &&
    payload !== null &&
    "success" in payload &&
    (payload as { success?: unknown }).success === false &&
    ("code" in payload || "message" in payload || "error" in payload)
  );
}

function httpStatusCodeToLabel(status?: number): string {
  if (!status) {
    return "API_ERROR";
  }

  switch (status) {
    case 400:
      return "BAD_REQUEST";
    case 401:
      return "UNAUTHORIZED";
    case 403:
      return "FORBIDDEN";
    case 404:
      return "NOT_FOUND";
    case 409:
      return "CONFLICT";
    case 422:
      return "UNPROCESSABLE_ENTITY";
    default:
      return status >= 500 ? "SERVER_ERROR" : `HTTP_${status}`;
  }
}
