import { randomUUID } from "node:crypto";

export interface ProblemDetails {
  type: string;
  title: string;
  status: number;
  detail?: string;
  instance?: string;
  code?: string;
  [key: string]: unknown;
}

export interface ProblemDetailsInput {
  type?: string;
  title: string;
  status: number;
  detail?: string;
  instance?: string;
  code?: string;
  [key: string]: unknown;
}

export function problem(details: ProblemDetailsInput, headers?: HeadersInit): Response {
  const body: ProblemDetails = {
    ...details,
    type: details.type ?? "about:blank",
    title: details.title,
    status: details.status,
  };
  return Response.json(body, {
    status: body.status,
    headers: {
      "content-type": "application/problem+json; charset=utf-8",
      ...Object.fromEntries(new Headers(headers)),
    },
  });
}

export type ServiceToken<T> = string | symbol | { readonly key: symbol; readonly __type?: T };
export function serviceToken<T>(description: string): ServiceToken<T> {
  return { key: Symbol(description) };
}
function tokenKey<T>(token: ServiceToken<T>): string | symbol {
  return typeof token === "object" ? token.key : token;
}

export class ServiceContainer {
  readonly #values = new Map<string | symbol, unknown>();
  constructor(private readonly parent?: ServiceContainer) {}
  set<T>(token: ServiceToken<T>, value: T): this {
    this.#values.set(tokenKey(token), value);
    return this;
  }
  has<T>(token: ServiceToken<T>): boolean {
    return this.#values.has(tokenKey(token)) || !!this.parent?.has(token);
  }
  get<T>(token: ServiceToken<T>): T {
    const key = tokenKey(token);
    if (this.#values.has(key)) return this.#values.get(key) as T;
    if (this.parent) return this.parent.get(token);
    throw new Error(`WRN-SERVICE-NOT-FOUND: ${typeof key === "symbol" ? key.description : key}`);
  }
  tryGet<T>(token: ServiceToken<T>): T | undefined {
    try {
      return this.get(token);
    } catch {
      return undefined;
    }
  }
  scope(): ServiceContainer {
    return new ServiceContainer(this);
  }
}

export type LifecyclePhase = "starting" | "started" | "stopping" | "stopped";
export type LifecycleHandler = (signal: AbortSignal) => void | Promise<void>;
export class ApplicationLifecycle {
  readonly #handlers = new Map<LifecyclePhase, LifecycleHandler[]>();
  #controller = new AbortController();
  on(phase: LifecyclePhase, handler: LifecycleHandler): () => void {
    const handlers = this.#handlers.get(phase) ?? [];
    handlers.push(handler);
    this.#handlers.set(phase, handlers);
    return () => {
      const index = handlers.indexOf(handler);
      if (index >= 0) handlers.splice(index, 1);
    };
  }
  async run(phase: LifecyclePhase): Promise<void> {
    if (phase === "stopping") this.#controller.abort("application stopping");
    const handlers = this.#handlers.get(phase) ?? [];
    const sequence =
      phase === "stopping" || phase === "stopped" ? [...handlers].reverse() : handlers;
    for (const handler of sequence) await handler(this.#controller.signal);
  }
  get signal(): AbortSignal {
    return this.#controller.signal;
  }
}

export interface HealthCheckResult {
  status: "up" | "down" | "degraded";
  message?: string;
  details?: unknown;
  durationMs?: number;
}
export type HealthCheck = () => HealthCheckResult | Promise<HealthCheckResult>;
export class HealthRegistry {
  readonly #checks = new Map<string, HealthCheck>();
  register(name: string, check: HealthCheck): () => void {
    this.#checks.set(name, check);
    return () => this.#checks.delete(name);
  }
  async check(): Promise<{
    status: "up" | "down" | "degraded";
    checks: Record<string, HealthCheckResult>;
  }> {
    const checks: Record<string, HealthCheckResult> = {};
    for (const [name, check] of this.#checks) {
      const start = performance.now();
      try {
        checks[name] = {
          ...(await check()),
          durationMs: Math.round((performance.now() - start) * 100) / 100,
        };
      } catch (error) {
        checks[name] = {
          status: "down",
          message: error instanceof Error ? error.message : String(error),
          durationMs: Math.round((performance.now() - start) * 100) / 100,
        };
      }
    }
    const values = Object.values(checks);
    const status = values.some((item) => item.status === "down")
      ? "down"
      : values.some((item) => item.status === "degraded")
        ? "degraded"
        : "up";
    return { status, checks };
  }
}

export function requestId(headers: Headers, preferred?: string): string {
  const existing =
    preferred ?? headers.get("x-request-id") ?? headers.get("traceparent")?.split("-")[1];
  return existing && /^[A-Za-z0-9._:-]{8,128}$/.test(existing) ? existing : randomUUID();
}

export interface IdempotencyRecord<T = unknown> {
  key: string;
  value: T;
  expiresAt: number;
}
export interface IdempotencyStore<T = unknown> {
  get(key: string): Promise<IdempotencyRecord<T> | null>;
  set(record: IdempotencyRecord<T>): Promise<void>;
  delete(key: string): Promise<void>;
}
export function memoryIdempotencyStore<T = unknown>(
  now: () => number = Date.now,
): IdempotencyStore<T> {
  const records = new Map<string, IdempotencyRecord<T>>();
  return {
    async get(key) {
      const value = records.get(key);
      if (!value) return null;
      if (value.expiresAt <= now()) {
        records.delete(key);
        return null;
      }
      return value;
    },
    async set(record) {
      records.set(record.key, record);
    },
    async delete(key) {
      records.delete(key);
    },
  };
}

export async function withIdempotency<T>(
  store: IdempotencyStore<T>,
  key: string,
  execute: () => Promise<T>,
  ttlMs = 24 * 60 * 60 * 1000,
): Promise<{ value: T; replayed: boolean }> {
  if (!key.trim()) throw new TypeError("idempotency key cannot be empty");
  const existing = await store.get(key);
  if (existing) return { value: existing.value, replayed: true };
  const value = await execute();
  await store.set({ key, value, expiresAt: Date.now() + ttlMs });
  return { value, replayed: false };
}
