import type { JsonValue } from "../../shared/src/index";
import { ctx, sql, config, logger } from "./context";
import type { RequestPerformanceSample, OperationPerformanceSample } from "./types";

const maxPerformanceSamples = 3000;

function trimPerformanceBuffers(): void {
  while (ctx.requestPerformanceSamples.length > maxPerformanceSamples) ctx.requestPerformanceSamples.shift();
  while (ctx.operationPerformanceSamples.length > maxPerformanceSamples) ctx.operationPerformanceSamples.shift();
}

function classifyRequestComponent(pathname: string): string {
  if (pathname.startsWith("/api/microservices/") && pathname.includes("/proxy/")) return "microservice_proxy";
  if (pathname.startsWith("/api/microservices/")) return "microservice_registry";
  if (pathname.startsWith("/api/nodes/")) return "node_metrics_api";
  if (pathname === "/api/nodes") return "node_inventory_api";
  if (pathname.startsWith("/api/tasks")) return "scheduler_api";
  if (pathname.startsWith("/api/schedules")) return "scheduler_api";
  if (pathname.startsWith("/api/events")) return "event_api";
  if (pathname.startsWith("/api/performance")) return "performance_api";
  if (pathname.startsWith("/api/")) return "core_api";
  if (pathname.startsWith("/ws/")) return "websocket_api";
  if (pathname === "/logs") return "logs_api";
  return "core_http";
}

export function recordRequestPerformance(req: Request, pathname: string, response: Response | undefined, durationMs: number): void {
  const status = response?.status ?? 101;
  ctx.requestPerformanceSamples.push({
    at: new Date().toISOString(),
    component: classifyRequestComponent(pathname),
    method: req.method,
    path: pathname,
    status,
    durationMs,
    ok: status < 400,
  });
  trimPerformanceBuffers();
}

export function recordOperationPerformance(service: string, operation: string, detail: string, durationMs: number, ok: boolean): void {
  ctx.operationPerformanceSamples.push({
    at: new Date().toISOString(),
    service,
    operation,
    durationMs,
    ok,
    detail: detail.length > 260 ? `${detail.slice(0, 257)}...` : detail,
  });
  trimPerformanceBuffers();
}
export async function withPerformanceOperation<T>(service: string, operation: string, detail: string, fn: () => Promise<T>): Promise<T> {
  const started = performance.now();
  try {
    const result = await fn();
    recordOperationPerformance(service, operation, detail, performance.now() - started, true);
    return result;
  } catch (error) {
    recordOperationPerformance(service, operation, error instanceof Error ? error.message : String(error), performance.now() - started, false);
    throw error;
  }
}

function percentile(values: number[], ratio: number): number {
  if (values.length === 0) return 0;
  const sorted = [...values].sort((left, right) => left - right);
  const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * ratio) - 1));
  return sorted[index] ?? 0;
}

function average(values: number[]): number {
  if (values.length === 0) return 0;
  return values.reduce((sum, value) => sum + value, 0) / values.length;
}

function roundMs(value: number): number {
  return Math.round(value * 10) / 10;
}

function summarizeRequestPerformance(): JsonValue[] {
  const groups = new Map<string, RequestPerformanceSample[]>();
  for (const sample of ctx.requestPerformanceSamples) {
    const rows = groups.get(sample.component) ?? [];
    rows.push(sample);
    groups.set(sample.component, rows);
  }
  return Array.from(groups.entries()).map(([component, rows]) => {
    const durations = rows.map((row) => row.durationMs);
    const failed = rows.filter((row) => !row.ok).length;
    return {
      component,
      requestCount: rows.length,
      failureCount: failed,
      failureRate: rows.length === 0 ? 0 : failed / rows.length,
      averageLatencyMs: roundMs(average(durations)),
      p95LatencyMs: roundMs(percentile(durations, 0.95)),
      maxLatencyMs: roundMs(Math.max(0, ...durations)),
    };
  }).sort((left, right) => Number((right as Record<string, JsonValue>).requestCount ?? 0) - Number((left as Record<string, JsonValue>).requestCount ?? 0)) as JsonValue[];
}
function summarizeOperationPerformance(): JsonValue[] {
  const groups = new Map<string, OperationPerformanceSample[]>();
  for (const sample of ctx.operationPerformanceSamples) {
    const key = `${sample.service}:${sample.operation}`;
    const rows = groups.get(key) ?? [];
    rows.push(sample);
    groups.set(key, rows);
  }
  return Array.from(groups.entries()).map(([key, rows]) => {
    const [service, ...operationParts] = key.split(":");
    const durations = rows.map((row) => row.durationMs);
    const failed = rows.filter((row) => !row.ok).length;
    return {
      service,
      operation: operationParts.join(":"),
      count: rows.length,
      failureCount: failed,
      averageLatencyMs: roundMs(average(durations)),
      p95LatencyMs: roundMs(percentile(durations, 0.95)),
      maxLatencyMs: roundMs(Math.max(0, ...durations)),
    };
  }).sort((left, right) => Number((right as Record<string, JsonValue>).count ?? 0) - Number((left as Record<string, JsonValue>).count ?? 0)) as JsonValue[];
}

async function getCodexQueueStoragePerformance(): Promise<JsonValue> {
  try {
    const rows = await sql()<Array<{ status: string; count: string | number }>>`
      SELECT status, count(*)::int AS count
      FROM unidesk_codex_queue_tasks
      GROUP BY status
      ORDER BY status ASC
    `;
    return {
      ok: true,
      table: "unidesk_codex_queue_tasks",
      counts: Object.fromEntries(rows.map((row) => [row.status, Number(row.count)])) as Record<string, number>,
      total: rows.reduce((sum, row) => sum + Number(row.count), 0),
    };
  } catch (error) {
    return {
      ok: false,
      table: "unidesk_codex_queue_tasks",
      error: error instanceof Error ? error.message : String(error),
    };
  }
}

async function getPgdataUsage(): Promise<JsonValue> {
  const rows = await sql()<Array<{ database_name: string; database_bytes: string | number; database_pretty: string }>>`
    SELECT
      current_database()::text AS database_name,
      pg_database_size(current_database())::bigint AS database_bytes,
      pg_size_pretty(pg_database_size(current_database()))::text AS database_pretty
  `;
  const row = rows[0];
  return {
    volumeName: config().databaseVolumeName,
    volumeSize: config().databaseVolumeSize,
    databaseName: row?.database_name ?? "unknown",
    databaseBytes: Number(row?.database_bytes ?? 0),
    databasePretty: row?.database_pretty ?? "--",
  };
}
export async function getPerformance(): Promise<JsonValue> {
  const memory = process.memoryUsage();
  const pgdata = await withPerformanceOperation("database", "pgdata_usage", "pg_database_size", () => getPgdataUsage());
  const codexQueueStorage = await withPerformanceOperation("database", "codex_queue_storage", "unidesk_codex_queue_tasks", () => getCodexQueueStoragePerformance());
  const recentFailures = ctx.requestPerformanceSamples
    .filter((sample) => !sample.ok)
    .slice(-20)
    .reverse()
    .map((sample) => ({ ...sample }) as Record<string, JsonValue>);
  const recentOperationCutoff = Date.now() - 10 * 60 * 1000;
  const recentSlowOperations = ctx.operationPerformanceSamples
    .filter((sample) => {
      const at = Date.parse(sample.at);
      return Number.isFinite(at) && at >= recentOperationCutoff;
    })
    .sort((left, right) => Date.parse(right.at) - Date.parse(left.at))
    .slice(0, 20)
    .map((sample) => ({ ...sample }) as Record<string, JsonValue>);
  return {
    ok: true,
    service: "backend-core",
    generatedAt: new Date().toISOString(),
    startedAt: ctx.serviceStartedAt.toISOString(),
    uptimeSeconds: Math.floor((Date.now() - ctx.serviceStartedAt.getTime()) / 1000),
    requests: {
      sampleCount: ctx.requestPerformanceSamples.length,
      componentSummary: summarizeRequestPerformance(),
      recentFailures,
    },
    operations: {
      sampleCount: ctx.operationPerformanceSamples.length,
      summary: summarizeOperationPerformance(),
      recentSlowOperations,
    },
    process: {
      rssBytes: memory.rss,
      heapUsedBytes: memory.heapUsed,
      heapTotalBytes: memory.heapTotal,
      externalBytes: memory.external,
      arrayBuffersBytes: memory.arrayBuffers,
    },
    database: {
      ready: ctx.dbReady,
      pgdata,
      codexQueueStorage,
    },
  };
}
