/**
 * Canonical V3 API envelope. It carries source provenance on every response
 * and represents unavailable reads with null data/metadata plus HTTP 503,
 * never plausible zero/default data.
 */

export const V3_ENVELOPE_SCHEMA_VERSION = "1.0.0";

export type V3Finality = "soft" | "safe" | "finalized";
export type V3ContractStatus = "active" | "legacy" | "deprecated" | "paused" | "read-only" | "unknown";
export type V3ResponseStatus = "ok" | "degraded";

export interface V3Envelope<T> {
  chainId: number | null;
  contractAddress: `0x${string}` | null;
  protocolVersion: "v3";
  contractStatus: V3ContractStatus;
  status: V3ResponseStatus;
  blockNumber: string | null;
  blockHash: `0x${string}` | null;
  observedAt: string;
  finality: V3Finality | null;
  dataSourceErrors: string[];
  schemaVersion: typeof V3_ENVELOPE_SCHEMA_VERSION;
  data: T | null;
}

export interface V3EnvelopeContext {
  chainId: number;
  contractAddress: `0x${string}`;
  contractStatus: Exclude<V3ContractStatus, "unknown">;
  blockNumber: bigint;
  blockHash: `0x${string}`;
  finality: V3Finality;
  observedAt?: string;
}

function describeError(error: unknown): string {
  if (error instanceof Error && error.message) return error.message;
  if (typeof error === "string" && error) return error;
  return "unknown data source error";
}

export function assertV3Envelope(value: unknown): asserts value is V3Envelope<unknown> {
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("V3 response must be an object envelope");
  const envelope = value as Partial<V3Envelope<unknown>>;
  const required = [
    "chainId", "contractAddress", "protocolVersion", "contractStatus", "status", "blockNumber", "blockHash",
    "observedAt", "finality", "dataSourceErrors", "schemaVersion", "data",
  ] as const;
  for (const field of required) if (!(field in envelope)) throw new TypeError(`V3 response omitted ${field}`);
  if (envelope.protocolVersion !== "v3") throw new TypeError("V3 response must declare protocolVersion v3");
  if (envelope.schemaVersion !== V3_ENVELOPE_SCHEMA_VERSION) throw new TypeError("V3 response has an unsupported schemaVersion");
  if (envelope.status !== "ok" && envelope.status !== "degraded") throw new TypeError("V3 response has an invalid status");
  if (!Array.isArray(envelope.dataSourceErrors) || envelope.dataSourceErrors.some((error) => typeof error !== "string" || !error)) {
    throw new TypeError("V3 response must expose non-empty string dataSourceErrors");
  }
  if (typeof envelope.observedAt !== "string" || Number.isNaN(Date.parse(envelope.observedAt))) {
    throw new TypeError("V3 response has an invalid observedAt timestamp");
  }
  if (envelope.status === "ok") {
    if (envelope.dataSourceErrors.length > 0 || envelope.data === null) throw new TypeError("healthy V3 response cannot conceal source errors or null data");
    if (typeof envelope.chainId !== "number" || !Number.isSafeInteger(envelope.chainId) || envelope.chainId <= 0) throw new TypeError("healthy V3 response requires chainId");
    if (typeof envelope.contractAddress !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(envelope.contractAddress)) throw new TypeError("healthy V3 response requires contractAddress");
    if (!/^\d+$/.test(envelope.blockNumber ?? "")) throw new TypeError("healthy V3 response requires blockNumber");
    if (typeof envelope.blockHash !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(envelope.blockHash)) throw new TypeError("healthy V3 response requires blockHash");
    if (!envelope.finality) throw new TypeError("healthy V3 response requires finality");
  } else if (envelope.dataSourceErrors.length === 0 || envelope.data !== null) {
    throw new TypeError("degraded V3 response must fail closed with errors and null data");
  }
}

export async function readV3Envelope<T>(
  context: V3EnvelopeContext,
  read: () => Promise<T>,
  now: () => Date = () => new Date(),
): Promise<V3Envelope<T>> {
  const observedAt = context.observedAt ?? now().toISOString();
  try {
    const data = await read();
    const envelope: V3Envelope<T> = {
      chainId: context.chainId,
      contractAddress: context.contractAddress,
      protocolVersion: "v3",
      contractStatus: context.contractStatus,
      status: "ok",
      blockNumber: context.blockNumber.toString(10),
      blockHash: context.blockHash,
      observedAt,
      finality: context.finality,
      dataSourceErrors: [],
      schemaVersion: V3_ENVELOPE_SCHEMA_VERSION,
      data,
    };
    assertV3Envelope(envelope);
    return envelope;
  } catch (error) {
    const envelope: V3Envelope<T> = {
      chainId: null,
      contractAddress: null,
      protocolVersion: "v3",
      contractStatus: "unknown",
      status: "degraded",
      blockNumber: null,
      blockHash: null,
      observedAt,
      finality: null,
      dataSourceErrors: [describeError(error)],
      schemaVersion: V3_ENVELOPE_SCHEMA_VERSION,
      data: null,
    };
    assertV3Envelope(envelope);
    return envelope;
  }
}

export function v3EnvelopeHttpStatus(envelope: V3Envelope<unknown>): 200 | 503 {
  assertV3Envelope(envelope);
  return envelope.status === "ok" ? 200 : 503;
}

export function createV3EnvelopeResponse<T>(envelope: V3Envelope<T>): Response {
  return Response.json(envelope, { status: v3EnvelopeHttpStatus(envelope) });
}
