import { randomUUID } from "node:crypto";

import {
  ENVIRONMENT_DEV,
  ERROR_CODES,
  HwlabProtocolError,
  JSON_RPC_VERSION,
  isProtocolEnvironment,
  validateRequest,
  validateResponse
} from "../protocol/index.mjs";
import {
  AUDIT_FIELD_NAMES,
  CLOUD_API_SERVICE_ID,
  createAuditRecord,
  deriveActorFromMeta
} from "../audit/index.mjs";
import { buildDbRuntimeReadiness } from "./db-contract.ts";
import { describeCodeAgentAvailability } from "./code-agent-chat.ts";
import { buildCloudApiReadiness } from "./health-contract.ts";
import { createCloudRuntimeStore } from "../db/runtime-store.ts";
import { M3_IO_RPC_METHODS, handleM3IoRpc } from "./m3-io-control.ts";
import { createGatewayShellRequest } from "./gateway-demo-registry.ts";

export const SUPPORTED_RPC_METHODS = Object.freeze([
  "system.health",
  "cloud.adapter.describe",
  M3_IO_RPC_METHODS.doWrite,
  M3_IO_RPC_METHODS.diRead,
  "gateway.session.register",
  "box.resource.register",
  "box.capability.report",
  "hardware.operation.request",
  "hardware.invoke.shell",
  "audit.event.write",
  "audit.event.query",
  "evidence.record.write",
  "evidence.record.query"
]);

const rpcHandlers = new Map([
  ["system.health", handleSystemHealth],
  ["cloud.adapter.describe", handleAdapterDescribe],
  [M3_IO_RPC_METHODS.doWrite, async (params, envelope, context) => handleM3IoRpc("do.write", params, envelope, context)],
  [M3_IO_RPC_METHODS.diRead, async (params, envelope, context) => handleM3IoRpc("di.read", params, envelope, context)],
  ["gateway.session.register", async (params, envelope, context) => getRuntimeStore(context).registerGatewaySession(params, envelope.meta)],
  ["box.resource.register", async (params, envelope, context) => getRuntimeStore(context).registerBoxResource(params, envelope.meta)],
  ["box.capability.report", async (params, envelope, context) => getRuntimeStore(context).reportBoxCapabilities(params, envelope.meta)],
  ["hardware.operation.request", async (params, envelope, context) => getRuntimeStore(context).requestHardwareOperation(params, envelope.meta)],
  ["hardware.invoke.shell", handleHardwareInvokeShell],
  ["audit.event.write", async (params, envelope, context) => getRuntimeStore(context).writeAuditEvent(params, envelope.meta)],
  ["audit.event.query", async (params, envelope, context) => getRuntimeStore(context).queryAuditEvents(params)],
  ["evidence.record.write", async (params, envelope, context) => getRuntimeStore(context).writeEvidenceRecord(params, envelope.meta)],
  ["evidence.record.query", async (params, envelope, context) => getRuntimeStore(context).queryEvidenceRecords(params)]
]);

const defaultRuntimeStore = createCloudRuntimeStore();

export async function handleJsonRpcRequest(envelope, context = {}) {
  const requestMeta = extractMeta(envelope);
  const requestId = envelope?.id ?? "req_unassigned";
  const method = typeof envelope?.method === "string" ? envelope.method : "method_unspecified";

  try {
    validateRequest(envelope);
  } catch (error) {
    return createErrorEnvelope({
      id: requestId,
      code: ERROR_CODES.invalidRequest,
      message: "Invalid JSON-RPC request",
      data: {
        reason: error.message
      },
      requestMeta,
      context: {
        ...context,
        operation: "json_rpc.validate",
        target: {
          type: "rpc_method",
          id: method
        }
      }
    });
  }

  const handler = rpcHandlers.get(envelope.method);
  if (!handler) {
    return createErrorEnvelope({
      id: envelope.id,
      code: ERROR_CODES.methodNotFound,
      message: `Method ${envelope.method} is not implemented by hwlab-cloud-api`,
      data: {
        method: envelope.method
      },
      requestMeta: envelope.meta,
      context: {
        ...context,
        operation: envelope.method,
        target: {
          type: "rpc_method",
          id: envelope.method
        },
        result: "rejected"
      }
    });
  }

  try {
    const result = await handler(envelope.params ?? {}, envelope, context);
    return createResultEnvelope({
      id: envelope.id,
      result,
      requestMeta: envelope.meta
    });
  } catch (error) {
    if (error instanceof HwlabProtocolError) {
      return createErrorEnvelope({
        id: envelope.id,
        code: error.code,
        message: error.message,
        data: error.data,
        requestMeta: envelope.meta,
        context: {
          ...context,
          ...error.context,
          operation: envelope.method,
          target: {
            type: "rpc_method",
            id: envelope.method
          },
          result: "rejected"
        }
      });
    }

    return createErrorEnvelope({
      id: envelope.id,
      code: ERROR_CODES.internalError,
      message: "hwlab-cloud-api failed to process the request",
      data: {
        reason: error.message
      },
      requestMeta: envelope.meta,
      context: {
        ...context,
        operation: envelope.method,
        target: {
          type: "rpc_method",
          id: envelope.method
        },
        result: "failed"
      }
    });
  }
}

export function createResultEnvelope({ id, result, requestMeta = {} }) {
  const envelope = {
    jsonrpc: JSON_RPC_VERSION,
    id,
    result,
    meta: createResponseMeta(requestMeta)
  };

  validateResponse(envelope);
  return envelope;
}

export function createErrorEnvelope({
  id = "req_unassigned",
  code = ERROR_CODES.hwlabUnknown,
  message = "HWLAB runtime error",
  data = {},
  requestMeta = {},
  context = {}
} = {}) {
  const responseId = id ?? "req_unassigned";
  const meta = createResponseMeta(requestMeta);
  const audit = createAuditRecord({
    requestId: responseId,
    actor: deriveActorFromMeta(requestMeta),
    source: {
      serviceId: CLOUD_API_SERVICE_ID,
      environment: ENVIRONMENT_DEV,
      traceId: meta.traceId,
      adapter: context.adapter || "json-rpc"
    },
    operation: context.operation || "json_rpc.error",
    target: context.target || {
      type: "cloud_api",
      id: CLOUD_API_SERVICE_ID
    },
    result: context.result || "failed",
    timestamp: context.timestamp
  });

  const envelope = {
    jsonrpc: JSON_RPC_VERSION,
    id: responseId,
    error: {
      code,
      message,
      data: {
        ...normalizeErrorData(data),
        audit
      }
    },
    meta
  };

  validateResponse(envelope);
  return envelope;
}

export function createResponseMeta(requestMeta = {}) {
  const meta = {
    traceId: requestMeta.traceId || `trc_${randomUUID()}`,
    serviceId: CLOUD_API_SERVICE_ID,
    environment: isProtocolEnvironment(requestMeta.environment) ? requestMeta.environment : ENVIRONMENT_DEV
  };

  if (requestMeta.actorId) {
    meta.actorId = requestMeta.actorId;
  }

  return meta;
}

async function handleSystemHealth(params, envelope, context) {
  const env = context.env ?? process.env;
  const db = await buildDbRuntimeReadiness(env, context.dbProbe);
  const codeAgent = await describeCodeAgentAvailability(env, context);
  const runtime = await runtimeReadiness(getRuntimeStore(context));
  const readiness = buildCloudApiReadiness({ db, codeAgent, runtime });
  return {
    status: readiness.status,
    ready: readiness.ready,
    serviceId: CLOUD_API_SERVICE_ID,
    environment: ENVIRONMENT_DEV,
    db,
    codeAgent,
    runtime,
    readiness,
    blockers: readiness.blockers,
    blockerCodes: readiness.blockerCodes
  };
}

async function handleAdapterDescribe(params, envelope, context) {
  const env = context.env ?? process.env;
  const db = await buildDbRuntimeReadiness(env, context.dbProbe);
  const codeAgent = await describeCodeAgentAvailability(env, context);
  const runtime = await runtimeReadiness(getRuntimeStore(context));
  const readiness = buildCloudApiReadiness({ db, codeAgent, runtime });
  return {
    serviceId: CLOUD_API_SERVICE_ID,
    status: readiness.status,
    ready: readiness.ready,
    rpcEndpoint: "POST /rpc",
    restEndpoint: "POST /v1/rpc/{method}",
    methods: SUPPORTED_RPC_METHODS,
    auditFields: AUDIT_FIELD_NAMES,
    db,
    codeAgent,
    runtime,
    readiness,
    blockers: readiness.blockers,
    blockerCodes: readiness.blockerCodes
  };
}

function getRuntimeStore(context = {}) {
  return context.runtimeStore || defaultRuntimeStore;
}

async function handleHardwareInvokeShell(params, envelope, context) {
  const registry = context.gatewayRegistry;
  const operationId = params.operationId ?? `op_gateway_shell_${randomUUID()}`;
  const dispatchParams = {
    ...params,
    operationId
  };
  const gatewaySessionId = dispatchParams.gatewaySessionId;
  if (!registry?.isOnline?.(gatewaySessionId)) {
    return getRuntimeStore(context).invokeHardwareShell(dispatchParams, envelope.meta);
  }

  const dispatch = await registry.enqueue({
    gatewaySessionId,
    request: createGatewayShellRequest({
      id: envelope.id,
      params: dispatchParams,
      meta: envelope.meta
    }),
    timeoutMs: parseDispatchTimeout(context.env ?? process.env, dispatchParams)
  });

  if (!dispatch.ok) {
    return {
      accepted: true,
      status: dispatch.status === "timed_out" ? "timed_out" : "accepted",
      operationId,
      gatewaySessionId,
      resourceId: dispatchParams.resourceId ?? null,
      capabilityId: dispatchParams.capabilityId ?? null,
      dispatch: {
        shellExecuted: false,
        dispatchStatus: dispatch.status,
        message: dispatch.error ?? dispatch.response?.error?.message ?? "gateway dispatch failed",
        error: dispatch.response?.error ?? null
      }
    };
  }

  const gatewayResult = dispatch.response?.result ?? {};
  return {
    accepted: gatewayResult.accepted !== false,
    status: gatewayResult.status ?? statusFromShellResult(gatewayResult),
    operationId: gatewayResult.operationId ?? operationId,
    gatewaySessionId: gatewayResult.gatewaySessionId ?? gatewaySessionId,
    resourceId: dispatchParams.resourceId ?? null,
    capabilityId: dispatchParams.capabilityId ?? null,
    dispatch: {
      shellExecuted: gatewayResult.shellExecuted === true,
      dispatchStatus: gatewayResult.dispatchStatus ?? statusFromShellResult(gatewayResult),
      ...gatewayResult
    },
    auditId: gatewayResult.auditId ?? null,
    evidenceId: gatewayResult.evidenceId ?? null,
    gateway: gatewayResult.gateway ?? null
  };
}

function statusFromShellResult(result = {}) {
  if (result.timedOut) return "timed_out";
  if (result.shellExecuted === false) return "rejected";
  if (Number.isInteger(result.exitCode)) return result.exitCode === 0 ? "succeeded" : "failed";
  return "completed";
}

function parseDispatchTimeout(env, params = {}) {
  const configured = parsePositiveInteger(env.HWLAB_GATEWAY_DISPATCH_TIMEOUT_MS, null);
  const requested = parsePositiveInteger(params?.input?.timeoutMs ?? params?.timeoutMs, null);
  const base = Math.max(configured ?? 0, requested ?? 0, 120000);
  const graceMs = parsePositiveInteger(env.HWLAB_GATEWAY_DISPATCH_TIMEOUT_GRACE_MS, 10000);
  const bounded = Math.min(Math.max(base + graceMs, 1000), 610000);
  return bounded;
}

function parsePositiveInteger(value, fallback) {
  const parsed = Number.parseInt(value ?? "", 10);
  return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
}

async function runtimeReadiness(runtimeStore) {
  if (typeof runtimeStore?.readiness === "function") {
    return runtimeStore.readiness();
  }
  return runtimeStore.summary();
}

function extractMeta(envelope) {
  if (envelope && typeof envelope === "object" && !Array.isArray(envelope) && envelope.meta) {
    return envelope.meta;
  }
  return {};
}

function normalizeErrorData(data) {
  if (!data || typeof data !== "object" || Array.isArray(data)) {
    return {};
  }
  return data;
}
