/**
 * Federation wire-format error envelope and exception hierarchy.
 *
 * Source of truth: docs/federation/PRD.md §6, §8.
 *
 * DESIGN: Typed error classes rather than discriminated union values
 * ──────────────────────────────────────────────────────────────────
 * We expose:
 *   1. `FEDERATION_ERROR_CODES` — closed string-enum tuple (exhaustiveness-checkable).
 *   2. `FederationErrorCode` — union type inferred from the tuple.
 *   3. `FederationErrorEnvelopeSchema` — Zod schema for the wire format.
 *   4. `FederationError` — base Error subclass with a typed `code` property.
 *      One concrete subclass per code (e.g. `FederationUnauthorizedError`),
 *      which enables `instanceof` dispatch in handlers without a switch.
 *
 * Rationale: subclasses give gateway handlers and the client a clean dispatch
 * point (catch + instanceof) without re-parsing or switch tables.  All classes
 * carry `code` so a generic logger can act on any FederationError uniformly.
 *
 * Pure — no NestJS, no DB, no Node-only APIs. Safe for browser/edge.
 */

import { z } from 'zod';

// ---------------------------------------------------------------------------
// Error code enum (closed)
// ---------------------------------------------------------------------------

export const FEDERATION_ERROR_CODES = [
  'unauthorized',
  'forbidden',
  'not_found',
  'rate_limited',
  'scope_violation',
  'invalid_request',
  'internal_error',
] as const;

export type FederationErrorCode = (typeof FEDERATION_ERROR_CODES)[number];

// ---------------------------------------------------------------------------
// Wire-format schema
// ---------------------------------------------------------------------------

export const FederationErrorEnvelopeSchema = z.object({
  error: z.object({
    code: z.enum(FEDERATION_ERROR_CODES),
    message: z.string(),
    details: z.unknown().optional(),
  }),
});

export type FederationErrorEnvelope = z.infer<typeof FederationErrorEnvelopeSchema>;

// ---------------------------------------------------------------------------
// Exception class hierarchy
// ---------------------------------------------------------------------------

/**
 * Base class for all federation errors.
 * Carries a typed `code` so handlers can act uniformly on any FederationError.
 */
export class FederationError extends Error {
  readonly code: FederationErrorCode;
  readonly details?: unknown;

  constructor(code: FederationErrorCode, message: string, details?: unknown) {
    super(message);
    this.name = 'FederationError';
    this.code = code;
    this.details = details;
  }

  /** Serialise to the wire-format error envelope. */
  toEnvelope(): FederationErrorEnvelope {
    return {
      error: {
        code: this.code,
        message: this.message,
        ...(this.details !== undefined ? { details: this.details } : {}),
      },
    };
  }
}

/** Client cert is missing, invalid, or signed by an untrusted CA. */
export class FederationUnauthorizedError extends FederationError {
  constructor(message = 'Unauthorized', details?: unknown) {
    super('unauthorized', message, details);
    this.name = 'FederationUnauthorizedError';
  }
}

/** Grant is inactive, revoked, or the subject user lacks access to the resource. */
export class FederationForbiddenError extends FederationError {
  constructor(message = 'Forbidden', details?: unknown) {
    super('forbidden', message, details);
    this.name = 'FederationForbiddenError';
  }
}

/** Requested resource does not exist. */
export class FederationNotFoundError extends FederationError {
  constructor(message = 'Not found', details?: unknown) {
    super('not_found', message, details);
    this.name = 'FederationNotFoundError';
  }
}

/** Grant has exceeded its rate limit; Retry-After should accompany this. */
export class FederationRateLimitedError extends FederationError {
  constructor(message = 'Rate limit exceeded', details?: unknown) {
    super('rate_limited', message, details);
    this.name = 'FederationRateLimitedError';
  }
}

/**
 * The request targets a resource or performs an action that the grant's
 * scope explicitly disallows (distinct from generic 403 — scope_violation
 * means the scope configuration itself blocked the request).
 */
export class FederationScopeViolationError extends FederationError {
  constructor(message = 'Scope violation', details?: unknown) {
    super('scope_violation', message, details);
    this.name = 'FederationScopeViolationError';
  }
}

/** Malformed request — missing fields, invalid cursor, unknown verb, etc. */
export class FederationInvalidRequestError extends FederationError {
  constructor(message = 'Invalid request', details?: unknown) {
    super('invalid_request', message, details);
    this.name = 'FederationInvalidRequestError';
  }
}

/** Unexpected server-side failure. */
export class FederationInternalError extends FederationError {
  constructor(message = 'Internal error', details?: unknown) {
    super('internal_error', message, details);
    this.name = 'FederationInternalError';
  }
}

// ---------------------------------------------------------------------------
// Typed parser
// ---------------------------------------------------------------------------

/**
 * Parse an unknown value as a FederationErrorEnvelope.
 * Throws a plain Error (not FederationError) when parsing fails — this means
 * the payload wasn't even a valid error envelope.
 */
export function parseFederationErrorEnvelope(input: unknown): FederationErrorEnvelope {
  const result = FederationErrorEnvelopeSchema.safeParse(input);
  if (!result.success) {
    const issues = result.error.issues
      .map((e) => `  - [${e.path.join('.') || 'root'}] ${e.message}`)
      .join('\n');
    throw new Error(`Invalid federation error envelope:\n${issues}`);
  }
  return result.data;
}
