import { ORPCError } from "@orpc/contract";
import { Cause, Data } from "effect";
import type { z } from "zod";

export class PluginRuntimeError extends Data.TaggedError("PluginRuntimeError")<{
  readonly pluginId?: string;
  readonly operation?: string;
  readonly procedureName?: string;
  readonly cause?: Error;
  readonly retryable: boolean;
}> {}

export class ModuleFederationError extends Data.TaggedError("ModuleFederationError")<{
  readonly pluginId: string;
  readonly remoteUrl: string;
  readonly cause?: Error;
}> {}

export class ValidationError extends Data.TaggedError("ValidationError")<{
  readonly pluginId: string;
  readonly stage: "config" | "input" | "output" | "state";
  readonly zodError: z.ZodError;
}> {}

const extractErrorMessage = (error: unknown): string => {
  if (!error) return "Unknown error";

  if (error instanceof Error) {
    if (error.message) return error.message;
    if ((error as any).cause instanceof Error) {
      return extractErrorMessage((error as any).cause);
    }
  }

  if (error instanceof AggregateError && error.errors?.length) {
    return error.errors.map((e) => extractErrorMessage(e)).join("; ");
  }

  if (typeof error === "object" && "message" in error) {
    return String((error as any).message);
  }

  return String(error);
};

const formatValidationIssue = (issue: any, index: number, maxDisplay: number): string => {
  if (index >= maxDisplay) return "";

  const path =
    Array.isArray(issue.path) && issue.path.length > 0
      ? issue.path.join(".")
      : issue.path || "root";

  const message = issue.message || "Validation failed";

  return `│    ${index + 1}. ${path}: ${message}`;
};

const formatDataPreview = (data: unknown, maxLength = 100): string => {
  if (!data) return "undefined";

  try {
    const str = JSON.stringify(data);
    if (str.length <= maxLength) return str;

    if (typeof data === "object" && data !== null) {
      if (Array.isArray(data)) {
        return `Array(${data.length}) [...]`;
      }
      const keys = Object.keys(data);
      return `{ ${keys.slice(0, 3).join(", ")}${keys.length > 3 ? ", ..." : ""} }`;
    }

    return `${str.slice(0, maxLength)}...`;
  } catch {
    return String(data).slice(0, maxLength);
  }
};

const formatORPCValidationError = (error: any): string[] | null => {
  const cause = error?.cause || error;

  if (!cause?.issues || !Array.isArray(cause.issues) || cause.issues.length === 0) {
    return null;
  }

  const lines: string[] = [];
  const errorType = error?.message || cause?.message || "Validation failed";

  lines.push(`\n╭─ oRPC Validation Error ${"─".repeat(30)}`);
  lines.push(`│  ${errorType}`);
  lines.push(`│`);

  const maxDisplay = 10;
  const totalIssues = cause.issues.length;

  lines.push(`│  Issues (${totalIssues}):`);

  const displayedIssues = cause.issues.slice(0, maxDisplay);
  displayedIssues.forEach((issue: any, idx: number) => {
    const formatted = formatValidationIssue(issue, idx, maxDisplay);
    if (formatted) lines.push(formatted);
  });

  if (totalIssues > maxDisplay) {
    lines.push(`│    ... and ${totalIssues - maxDisplay} more`);
  }

  if (cause.data !== undefined) {
    lines.push(`│`);
    lines.push(`│  Data preview: ${formatDataPreview(cause.data, 80)}`);
  }

  lines.push(`╰${"─".repeat(50)}\n`);

  return lines;
};

export const formatORPCError = (error: any): void => {
  if (!(error instanceof ORPCError)) {
    return;
  }

  const validationLines = formatORPCValidationError(error);
  if (validationLines) {
    console.error(validationLines.join("\n"));
    return;
  }

  const lines: string[] = [];
  const code = error.code || "UNKNOWN";
  const status = error.status || 500;
  const message = error.message || "An error occurred";

  lines.push(`\n╭─ oRPC Error ${"─".repeat(40)}`);
  lines.push(`│  ${message}`);
  lines.push(`│  Code: ${code} (${status})`);
  lines.push(`│`);

  if (error.data) {
    const dataType = typeof error.data;
    if (dataType === "object" && error.data !== null) {
      if ("retryAfter" in error.data) {
        lines.push(`│  Retry after: ${error.data.retryAfter} seconds`);
      }
      if ("remainingRequests" in error.data) {
        lines.push(`│  Remaining: ${error.data.remainingRequests} requests`);
      }
      if ("host" in error.data) {
        lines.push(`│  Host: ${error.data.host}`);
      }
      if ("port" in error.data) {
        lines.push(`│  Port: ${error.data.port}`);
      }
      if ("suggestion" in error.data) {
        lines.push(`│  → ${error.data.suggestion}`);
      }
      if ("resource" in error.data) {
        lines.push(`│  Resource: ${error.data.resource}`);
      }
      if ("resourceId" in error.data) {
        lines.push(`│  ID: ${error.data.resourceId}`);
      }
    }
  }

  switch (code) {
    case "UNAUTHORIZED":
      lines.push(`│  → Check your API key or credentials`);
      break;
    case "TOO_MANY_REQUESTS":
      lines.push(`│  → Wait before retrying`);
      break;
    case "SERVICE_UNAVAILABLE":
    case "BAD_GATEWAY":
    case "GATEWAY_TIMEOUT":
      lines.push(`│  → The service may be temporarily unavailable`);
      break;
    case "TIMEOUT":
      lines.push(`│  → The operation took too long`);
      break;
  }

  lines.push(`╰${"─".repeat(50)}\n`);
  console.error(lines.join("\n"));
};

const formatPluginError = (
  pluginId: string | undefined,
  operation: string | undefined,
  message: string,
): void => {
  const lines: string[] = [];

  lines.push(`\n╭─ Plugin Error ${"─".repeat(40)}`);
  if (pluginId) lines.push(`│  Plugin: ${pluginId}`);
  if (operation) lines.push(`│  During: ${operation}`);
  lines.push(`│`);

  if (message.includes("ECONNREFUSED")) {
    lines.push(`│  ❌ Connection refused`);
    lines.push(`│  `);
    lines.push(`│  A required service is not running.`);
    lines.push(`│  → Run: docker compose up -d`);
  } else if (message.includes("ENOTFOUND")) {
    lines.push(`│  ❌ Host not found`);
    lines.push(`│  `);
    lines.push(`│  Check your connection URL or network settings.`);
  } else if (message.includes("ETIMEDOUT") || message.includes("timeout")) {
    lines.push(`│  ❌ Connection timeout`);
    lines.push(`│  `);
    lines.push(`│  The service took too long to respond.`);
  } else if (message.includes("EACCES") || message.includes("permission")) {
    lines.push(`│  ❌ Permission denied`);
    lines.push(`│  `);
    lines.push(`│  Check credentials or access permissions.`);
  } else if (message.includes("401") || message.includes("unauthorized")) {
    lines.push(`│  ❌ Authentication failed`);
    lines.push(`│  `);
    lines.push(`│  Check your API key or credentials.`);
  } else {
    lines.push(`│  ❌ ${message}`);
  }

  lines.push(`╰${"─".repeat(50)}\n`);

  console.error(lines.join("\n"));
};

const isRetryableError = (message: string): boolean => {
  const retryablePatterns = ["ETIMEDOUT", "ECONNRESET", "timeout", "503", "429"];
  return retryablePatterns.some((p) => message.toLowerCase().includes(p.toLowerCase()));
};

// Helper to determine if an oRPC error code is retryable
export const isRetryableORPCCode = (code: string): boolean => {
  switch (code) {
    case "TOO_MANY_REQUESTS":
    case "SERVICE_UNAVAILABLE":
    case "BAD_GATEWAY":
    case "GATEWAY_TIMEOUT":
    case "TIMEOUT":
      return true;
    default:
      return false;
  }
};

// Convert ORPC errors from plugin procedures to PluginRuntimeError
export const wrapORPCError = (
  orpcError: ORPCError<string, unknown>,
  pluginId?: string,
  procedureName?: string,
  operation?: string,
): PluginRuntimeError => {
  const validationLines = formatORPCValidationError(orpcError);
  if (validationLines) {
    console.error(validationLines.join("\n"));
  }

  return new PluginRuntimeError({
    pluginId,
    operation,
    procedureName,
    retryable: isRetryableORPCCode(orpcError.code),
    cause: orpcError as Error,
  });
};

/**
 * Extracts the underlying error from Effect's FiberFailure wrapper.
 * When Effect.runPromise rejects, errors are wrapped in FiberFailure.
 * This extracts the original error so oRPC can handle it properly.
 */
export const extractFromFiberFailure = (error: unknown): unknown => {
  if (!error || typeof error !== "object") return error;

  if ("cause" in error) {
    const cause = (error as { cause?: unknown }).cause;

    if (cause && typeof cause === "object" && "_tag" in cause) {
      try {
        const squashed = Cause.squash(cause as Cause.Cause<unknown>);
        if (squashed instanceof ORPCError) {
          return squashed;
        }
        return squashed;
      } catch {
        // Not a valid Cause
      }
    }

    if (cause instanceof ORPCError) {
      return cause;
    }
  }

  return error;
};

// Universal error converter for the runtime
export const toPluginRuntimeError = (
  error: unknown,
  pluginId?: string,
  procedureName?: string,
  operation?: string,
  defaultRetryable = false,
): PluginRuntimeError => {
  if (error instanceof ORPCError) {
    return wrapORPCError(error, pluginId, procedureName, operation);
  }

  if (error instanceof PluginRuntimeError) {
    return error;
  }

  const validationLines = formatORPCValidationError(error);
  if (validationLines) {
    console.error(validationLines.join("\n"));
  } else {
    const message = extractErrorMessage(error);
    formatPluginError(pluginId, operation, message);
  }

  return new PluginRuntimeError({
    pluginId,
    operation,
    procedureName,
    retryable: defaultRetryable || isRetryableError(extractErrorMessage(error)),
    cause: error instanceof Error ? error : new Error(extractErrorMessage(error)),
  });
};
