import {
  getComplianceCsv,
  getComplianceOcsf,
  getCompliancePdfReport,
  type ScanBinaryResult,
} from "@/actions/scans";
import { getTask } from "@/actions/task";
import { auth } from "@/auth.config";
import { useToast } from "@/components/shadcn";
import {
  COMPLIANCE_REPORT_DISPLAY_NAMES,
  type ComplianceReportType,
} from "@/lib/compliance/compliance-report-types";
import { readEnv } from "@/lib/runtime-env";
import { AuthSocialProvider, MetaDataProps, PermissionInfo } from "@/types";

export const baseUrl = process.env.AUTH_URL;
export const apiBaseUrl = readEnv(
  "UI_API_BASE_URL",
  "NEXT_PUBLIC_API_BASE_URL",
);

/**
 * Extracts a form value from a FormData object
 * @param formData - The FormData object to extract from
 * @param field - The name of the field to extract
 * @returns The value of the field
 */
export const getFormValue = (formData: FormData, field: string) =>
  formData.get(field);

/**
 * Filters out empty values from an object
 * @param obj - Object to filter
 * @returns New object with empty values removed
 * Avoids sending empty values to the API
 */
export function filterEmptyValues(
  obj: Record<string, any>,
): Record<string, any> {
  return Object.fromEntries(
    Object.entries(obj).filter(([_, value]) => {
      // Keep number 0 and boolean false as they are valid values
      if (value === 0 || value === false) return true;

      // Filter out null, undefined, empty strings, and empty arrays
      if (value === null || value === undefined) return false;
      if (typeof value === "string" && value.trim() === "") return false;
      if (Array.isArray(value) && value.length === 0) return false;

      return true;
    }),
  );
}

/**
 * Returns the authentication headers for API requests
 * @param options - Optional configuration options
 * @returns Authentication headers with Accept and Authorization
 */
export const getAuthHeaders = async (options?: { contentType?: boolean }) => {
  const session = await auth();

  const headers: Record<string, string> = {
    Accept: "application/vnd.api+json",
    Authorization: `Bearer ${session?.accessToken}`,
  };

  if (options?.contentType) {
    headers["Content-Type"] = "application/vnd.api+json";
  }

  return headers;
};

export const getAuthUrl = (provider: AuthSocialProvider) => {
  const config = {
    google: {
      baseUrl: "https://accounts.google.com/o/oauth2/v2/auth",
      params: {
        redirect_uri: process.env.SOCIAL_GOOGLE_OAUTH_CALLBACK_URL,
        prompt: "consent",
        response_type: "code",
        client_id: process.env.SOCIAL_GOOGLE_OAUTH_CLIENT_ID,
        scope: "openid email profile",
        access_type: "offline",
      },
    },
    github: {
      baseUrl: "https://github.com/login/oauth/authorize",
      params: {
        client_id: process.env.SOCIAL_GITHUB_OAUTH_CLIENT_ID,
        redirect_uri: process.env.SOCIAL_GITHUB_OAUTH_CALLBACK_URL,
        scope: "user:email",
      },
    },
  };

  const { baseUrl, params } = config[provider];
  const url = new URL(baseUrl);

  Object.entries(params).forEach(([key, value]) => {
    url.searchParams.set(key, value || "");
  });

  return url.toString();
};

const REPORT_PREPARATION_ERROR =
  "Unable to prepare the scan report. Please try again in a few minutes.";

export const GENERIC_SERVER_ERROR_MESSAGE =
  "Server is temporarily unavailable. Please try again in a few minutes.";

const HTML_ERROR_PATTERN =
  /(?:<!doctype\s+html|<html\b|<\/?(?:head|title|body|center|h1|h2|pre)\b)/i;

export const sanitizeErrorMessage = (
  message: string,
  fallback = GENERIC_SERVER_ERROR_MESSAGE,
): string => {
  const trimmedMessage = message.trim();

  if (!trimmedMessage) {
    return fallback;
  }

  return HTML_ERROR_PATTERN.test(trimmedMessage) ? fallback : trimmedMessage;
};

const getPreflightErrorMessage = async (response: Response) => {
  const contentType = response.headers.get("content-type")?.toLowerCase() || "";

  if (contentType.includes("text/html")) {
    return REPORT_PREPARATION_ERROR;
  }

  return (await response.text()) || "An unknown error occurred.";
};

export const downloadScanZip = async (
  scanId: string,
  toast: ReturnType<typeof useToast>["toast"],
) => {
  const reportUrl = `/api/scans/${encodeURIComponent(scanId)}/report`;

  try {
    const preflightResponse = await fetch(`${reportUrl}?preflight=1`, {
      cache: "no-store",
    });

    if (preflightResponse.status === 202) {
      toast({
        title: "The report is still being generated",
        description: "Please try again in a few minutes.",
      });
      return;
    }

    if (!preflightResponse.ok) {
      toast({
        variant: "destructive",
        title: "Download Failed",
        description: await getPreflightErrorMessage(preflightResponse),
      });
      return;
    }
  } catch (_error) {
    toast({
      variant: "destructive",
      title: "Download Failed",
      description: "Unable to start the report download. Please try again.",
    });
    return;
  }

  const a = document.createElement("a");
  a.href = reportUrl;
  a.download = `scan-${scanId}-report.zip`;
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);

  toast({
    title: "Download Started",
    description: "Your browser is downloading the scan report.",
  });
};

/**
 * Generic function to download a file from base64 data.
 *
 * Exported so feature-local wrappers (e.g. the cross-provider PDF download in
 * app/(prowler)/compliance/_lib) reuse the base64→blob handling instead of
 * duplicating it; those wrappers cannot live here because their server
 * actions import the @/lib barrel, which would create a cycle.
 */
export const downloadFile = async (
  result: ScanBinaryResult,
  outputType: string,
  successMessage: string,
  toast: ReturnType<typeof useToast>["toast"],
): Promise<void> => {
  if ("pending" in result && result.pending) {
    toast({
      title: "The report is still being generated",
      description: "Please try again in a few minutes.",
    });
    return;
  }

  if ("success" in result && result.success) {
    try {
      const binaryString = window.atob(result.data);
      const bytes = new Uint8Array(binaryString.length);
      for (let i = 0; i < binaryString.length; i++) {
        bytes[i] = binaryString.charCodeAt(i);
      }

      const blob = new Blob([bytes], { type: outputType });
      const url = window.URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = result.filename;
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      window.URL.revokeObjectURL(url);

      toast({
        title: "Download Complete",
        description: successMessage,
      });
    } catch (_error) {
      toast({
        variant: "destructive",
        title: "Download Failed",
        description: "An error occurred while processing the file.",
      });
    }
    return;
  }

  if ("error" in result) {
    toast({
      variant: "destructive",
      title: "Download Failed",
      description: result.error,
    });
    return;
  }

  // Unexpected case
  toast({
    variant: "destructive",
    title: "Download Failed",
    description: "Unexpected response. Please try again later.",
  });
};

export const downloadComplianceCsv = async (
  scanId: string,
  complianceId: string,
  toast: ReturnType<typeof useToast>["toast"],
): Promise<void> => {
  toast({
    title: "Download Started",
    description: "Preparing the CSV report. This may take a moment.",
  });
  const result = await getComplianceCsv(scanId, complianceId);
  await downloadFile(
    result,
    "text/csv",
    "The compliance report has been downloaded successfully.",
    toast,
  );
};

/**
 * Download the per-framework OCSF JSON export.
 *
 * Only universal frameworks declaring an ``outputs`` block produce this
 * artifact (currently DORA and CSA CCM 4.0); callers must gate the call
 * via ``isOcsfSupported`` to avoid surfacing a broken download on
 * frameworks the API will 404 on.
 */
export const downloadComplianceOcsf = async (
  scanId: string,
  complianceId: string,
  toast: ReturnType<typeof useToast>["toast"],
): Promise<void> => {
  toast({
    title: "Download Started",
    description: "Preparing the OCSF report. This may take a moment.",
  });
  const result = await getComplianceOcsf(scanId, complianceId);
  await downloadFile(
    result,
    "application/json",
    "The compliance OCSF report has been downloaded successfully.",
    toast,
  );
};

/**
 * Download a compliance PDF report.
 *
 * The call hits ``/scans/{id}/{reportType}`` for every supported framework.
 * For CIS — which ships multiple versions per provider — the backend only
 * generates the PDF for the highest available version, so the call site
 * does not need to resolve a specific variant.
 *
 * @param scanId - The scan ID
 * @param reportType - Type of report (from COMPLIANCE_REPORT_TYPES)
 * @param toast - Toast notification function
 */
export const downloadCompliancePdf = async (
  scanId: string,
  reportType: ComplianceReportType,
  toast: ReturnType<typeof useToast>["toast"],
): Promise<void> => {
  const reportName = COMPLIANCE_REPORT_DISPLAY_NAMES[reportType];
  toast({
    title: "Download Started",
    description: `Preparing the ${reportName} PDF report. This may take a moment.`,
  });
  const result = await getCompliancePdfReport(scanId, reportType);
  await downloadFile(
    result,
    "application/pdf",
    `The ${reportName} PDF report has been downloaded successfully.`,
    toast,
  );
};

/**
 * @deprecated Use {@link downloadCompliancePdf} instead. Kept as a thin
 * wrapper for callers not yet migrated.
 */
export const downloadComplianceReportPdf = async (
  scanId: string,
  reportType: ComplianceReportType,
  toast: ReturnType<typeof useToast>["toast"],
): Promise<void> => downloadCompliancePdf(scanId, reportType, toast);

export const isGoogleOAuthEnabled =
  !!process.env.SOCIAL_GOOGLE_OAUTH_CLIENT_ID &&
  !!process.env.SOCIAL_GOOGLE_OAUTH_CLIENT_SECRET;

export const isGithubOAuthEnabled =
  !!process.env.SOCIAL_GITHUB_OAUTH_CLIENT_ID &&
  !!process.env.SOCIAL_GITHUB_OAUTH_CLIENT_SECRET;

export const checkTaskStatus = async (
  taskId: string,
  maxRetries: number = 20,
  retryDelay: number = 1500,
): Promise<{ completed: boolean; error?: string }> => {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const task = await getTask(taskId);

    if (task.error) {
      console.error(`Error retrieving task: ${task.error}`);
      return { completed: false, error: task.error };
    }

    const state = task.data.attributes.state;

    switch (state) {
      case "completed":
        return { completed: true };
      case "failed":
        return { completed: false, error: task.data.attributes.result.error };
      case "available":
      case "scheduled":
      case "executing":
        // Continue waiting if the task is still in progress
        await new Promise((resolve) => setTimeout(resolve, retryDelay));
        break;
      default:
        return { completed: false, error: "Unexpected task state" };
    }
  }

  return { completed: false, error: "Max retries exceeded" };
};

export const wait = (ms: number) =>
  new Promise((resolve) => setTimeout(resolve, ms));

export const parseStringify = (value: any) => JSON.parse(JSON.stringify(value));

export const convertFileToUrl = (file: File) => URL.createObjectURL(file);

export const getPaginationInfo = (metadata: MetaDataProps) => {
  const currentPage = metadata?.pagination.page ?? "1";
  const totalPages = metadata?.pagination.pages;
  const totalEntries = metadata?.pagination.count;
  const itemsPerPageOptions = metadata?.pagination.itemsPerPage ?? [
    10, 20, 30, 50, 100,
  ];

  return { currentPage, totalPages, totalEntries, itemsPerPageOptions };
};

export function encryptKey(passkey: string) {
  return btoa(passkey);
}

export function decryptKey(passkey: string) {
  return atob(passkey);
}

export const getErrorMessage = (error: unknown): string => {
  if (error instanceof Error) {
    return sanitizeErrorMessage(error.message);
  } else if (error && typeof error === "object" && "message" in error) {
    return sanitizeErrorMessage(String(error.message));
  } else if (typeof error === "string") {
    return sanitizeErrorMessage(error);
  } else {
    return "Oops! Something went wrong.";
  }
};

export const permissionFormFields: PermissionInfo[] = [
  {
    field: "manage_users",
    label: "Invite and Manage Users",
    description: "Allows inviting new users and managing existing user details",
  },
  {
    field: "manage_account",
    label: "Manage Account",
    description: "Provides access to account settings and RBAC configuration",
  },
  {
    field: "unlimited_visibility",
    label: "Unlimited Visibility",
    description:
      "Grants organization-wide visibility across all providers, resources, findings, scans, and compliance results without granting admin actions.",
  },
  {
    field: "manage_providers",
    label: "Manage Providers",
    description: "Allows configuration and management of provider connections",
  },
  {
    field: "manage_integrations",
    label: "Manage Integrations",
    description:
      "Allows configuration and management of third-party integrations",
  },
  {
    field: "manage_scans",
    label: "Manage Scans",
    description: "Allows launching and configuring scans security scans",
  },
  {
    field: "manage_alerts",
    label: "Manage Alerts",
    description: "Allows creating and managing custom alerts",
  },

  {
    field: "manage_billing",
    label: "Manage Billing",
    description: "Provides access to billing settings and invoices",
  },
];
