import type { JsonValue } from "./types";

export type DatabaseTransientKind =
  | "deadlock"
  | "connection-closed"
  | "connection-reset"
  | "connection-timeout"
  | "connection-terminated"
  | "socket-write-null"
  | "unknown";

export interface DatabaseTransientClassification {
  transient: boolean;
  retryable: boolean;
  kind: DatabaseTransientKind | null;
  source: "postgres-deadlock" | "postgres-client" | "unknown";
  infrastructureBlocker: boolean;
  message: string;
  evidence: string[];
  recommendedAction: string;
}

export interface DirtyFlushBatch {
  taskIds: string[];
  queueIds: string[];
  takenAt: string;
}

export interface DatabaseStorageHealthInput {
  postgresReady: boolean;
  dirtyTaskCount: number;
  dirtyQueueCount: number;
  lastError: unknown;
  flushInFlight: boolean;
  clientRotationCount: number;
  lastClientRotationAt: string | null;
  lastClientRotationReason: string | null;
  transientUncaughtSuppressedCount: number;
  lastTransientUncaughtSuppressedAt: string | null;
  consecutiveFlushFailures: number;
  lastFlushFailureAt: string | null;
  nextFlushRetryAt: string | null;
}

function errorField(error: unknown, key: "code" | "errno"): string {
  if (typeof error !== "object" || error === null) return "";
  const value = (error as Record<string, unknown>)[key];
  return typeof value === "string" ? value : "";
}

export function databaseErrorText(error: unknown): string {
  if (error instanceof Error) return `${error.name}: ${error.message}\n${error.stack ?? ""}\n${errorField(error, "code")}\n${errorField(error, "errno")}`;
  return String(error);
}

function collectEvidence(text: string, patterns: RegExp[], limit = 5): string[] {
  const evidence: string[] = [];
  for (const pattern of patterns) {
    for (const match of text.matchAll(pattern)) {
      const value = String(match[0] ?? "").replace(/\s+/gu, " ").trim();
      if (value.length > 0 && !evidence.includes(value)) evidence.push(value.slice(0, 220));
      if (evidence.length >= limit) return evidence;
    }
  }
  return evidence;
}

export function classifyTransientDatabaseError(error: unknown): DatabaseTransientClassification {
  const text = databaseErrorText(error);
  const lower = text.toLowerCase();
  const deadlock = /deadlock detected/iu.test(text);
  const code = `${errorField(error, "code")}\n${errorField(error, "errno")}`;
  const postgresClientCode = /CONNECTION_CLOSED|CONNECTION_DESTROYED|CONNECTION_ENDED|CONNECT_TIMEOUT/iu.test(code);
  const postgresClient = postgresClientCode || /node_modules\/postgres|postgres(?:@\d+(?:\.\d+){0,2})?\/src|postgres\/src|d601-tcp-egress-gateway|:15432|unidesk_code_queue|set_config/iu.test(text);
  const combined = `${text}\n${code}`;
  const socketWriteNull = /socket\.write|null is not an object|cannot read propert(?:y|ies) ['"]?write|evaluating ['"]socket\.write/iu.test(combined);
  const connectionClosed = /CONNECTION_CLOSED|socket connection was closed|connection closed/iu.test(combined);
  const connectionReset = /ECONNRESET|EPIPE|broken pipe/iu.test(combined);
  const connectionTimeout = /ETIMEDOUT|CONNECT_TIMEOUT|Connection timeout|connect timed out/iu.test(combined);
  const connectionTerminated = /Connection terminated|CONNECTION_DESTROYED|CONNECTION_ENDED/iu.test(combined);
  const transient = deadlock || (postgresClient && (socketWriteNull || connectionClosed || connectionReset || connectionTimeout || connectionTerminated));
  const kind: DatabaseTransientKind | null = deadlock
    ? "deadlock"
    : socketWriteNull && postgresClient
      ? "socket-write-null"
      : connectionClosed && postgresClient
        ? "connection-closed"
        : connectionReset && postgresClient
          ? "connection-reset"
          : connectionTimeout && postgresClient
            ? "connection-timeout"
            : connectionTerminated && postgresClient
              ? "connection-terminated"
              : transient
                ? "unknown"
                : null;
  const evidence = collectEvidence(lower, [
    /postgres(?:@\d+(?:\.\d+){0,2})?\/src\/connection\.js:\d+/giu,
    /node_modules\/postgres\/src\/connection\.js:\d+/giu,
    /connection_closed/giu,
    /socket\.write/giu,
    /null is not an object/giu,
    /d601-tcp-egress-gateway[^\s'"]*/giu,
    /:15432/giu,
    /deadlock detected/giu,
    /econnreset|epipe|etimedout|connect_timeout/giu,
  ]);
  return {
    transient,
    retryable: transient,
    kind,
    source: deadlock ? "postgres-deadlock" : postgresClient ? "postgres-client" : "unknown",
    infrastructureBlocker: transient,
    message: transient
      ? "PostgreSQL client connection was transiently closed; rotate the client, keep dirty writes queued, and retry without crashing the scheduler."
      : "Error is not recognized as a transient PostgreSQL client failure.",
    evidence,
    recommendedAction: transient
      ? "Treat as Code Queue infrastructure, not task/business failure; inspect scheduler storage health and let bounded dirty-flush retry run before duplicating work."
      : "Handle through the caller's normal error path.",
  };
}

export function isTransientDatabaseError(error: unknown): boolean {
  return classifyTransientDatabaseError(error).transient;
}

export function takeDirtyFlushBatch(taskIds: Set<string>, queueIds: Set<string>, nowIso: string): DirtyFlushBatch {
  const batch = {
    taskIds: Array.from(taskIds).sort(),
    queueIds: Array.from(queueIds).sort(),
    takenAt: nowIso,
  };
  taskIds.clear();
  queueIds.clear();
  return batch;
}

export function restoreDirtyFlushBatch(batch: DirtyFlushBatch, taskIds: Set<string>, queueIds: Set<string>): void {
  for (const taskId of batch.taskIds) taskIds.add(taskId);
  for (const queueId of batch.queueIds) queueIds.add(queueId);
}

export function databaseStorageHealth(input: DatabaseStorageHealthInput): JsonValue {
  const classification = input.lastError === null ? null : classifyTransientDatabaseError(input.lastError);
  const dirtyCount = Math.max(0, input.dirtyTaskCount) + Math.max(0, input.dirtyQueueCount);
  const infrastructureBlocker = input.postgresReady !== true || input.lastError !== null || input.consecutiveFlushFailures > 0;
  const status = input.postgresReady !== true
    ? "unavailable"
    : infrastructureBlocker
      ? "degraded"
      : input.flushInFlight || dirtyCount > 0
        ? "recovering"
        : "ready";
  const signal = infrastructureBlocker
    ? [{
      id: classification?.kind === "socket-write-null" ? "postgres-client-socket-write-null" : "postgres-connection-rotation",
      category: "infrastructure-blocker",
      severity: input.postgresReady !== true ? "critical" : "high",
      retryable: classification?.retryable ?? true,
      bounded: true,
      message: classification?.message ?? "Code Queue PostgreSQL storage is not ready or has pending dirty writes after a flush failure.",
      evidence: classification?.evidence ?? [],
      commanderAction: classification?.recommendedAction ?? "Inspect Code Queue storage health and scheduler logs before treating affected tasks as failed.",
      source: "code-queue.storage.postgres",
    } as Record<string, JsonValue>]
    : [];
  return {
    status,
    degraded: infrastructureBlocker,
    infrastructureBlocker,
    postgresReady: input.postgresReady,
    dirtyTaskCount: input.dirtyTaskCount,
    dirtyQueueCount: input.dirtyQueueCount,
    dirtyCount,
    flushInFlight: input.flushInFlight,
    consecutiveFlushFailures: input.consecutiveFlushFailures,
    lastFlushFailureAt: input.lastFlushFailureAt,
    nextFlushRetryAt: input.nextFlushRetryAt,
    clientRotationCount: input.clientRotationCount,
    lastClientRotationAt: input.lastClientRotationAt,
    lastClientRotationReason: input.lastClientRotationReason,
    transientUncaughtSuppressedCount: input.transientUncaughtSuppressedCount,
    lastTransientUncaughtSuppressedAt: input.lastTransientUncaughtSuppressedAt,
    lastErrorKind: classification?.kind ?? null,
    lastErrorTransient: classification?.transient ?? false,
    lastErrorMessage: input.lastError === null ? null : databaseErrorText(input.lastError).split(/\r?\n/u)[0]?.slice(0, 500) ?? null,
    signals: signal as unknown as JsonValue,
  };
}
