import { createHash, randomUUID } from "node:crypto";

import { createRpcErrorBody, ENVIRONMENT_DEV, ERROR_CODES, isProtocolEnvironment } from "../protocol/index.mjs";
import { getPortDefinition } from "./model.mjs";

export const AUDIT_SHAPE_FIELDS = Object.freeze([
  "auditId",
  "traceId",
  "actorType",
  "actorId",
  "action",
  "targetType",
  "targetId",
  "serviceId",
  "environment",
  "occurredAt"
]);

export const EVIDENCE_SHAPE_FIELDS = Object.freeze([
  "evidenceId",
  "projectId",
  "operationId",
  "kind",
  "uri",
  "sha256",
  "serviceId",
  "environment",
  "createdAt"
]);

export function isoNow() {
  return new Date().toISOString();
}

export function makeId(prefix, value = randomUUID()) {
  const normalized = String(value)
    .trim()
    .replace(/[^A-Za-z0-9._:-]+/g, "_")
    .replace(/^_+|_+$/g, "");

  return `${prefix}_${normalized || randomUUID()}`;
}

export function operationContext(input = {}, { serviceId, actorId = `svc_${serviceId}`, now = isoNow() } = {}) {
  const traceId = input.traceId ?? makeId("trc", randomUUID());
  const operationId = input.operationId ?? makeId("op", randomUUID());
  const requestId = input.requestId ?? makeId("req", randomUUID());

  return {
    traceId,
    operationId,
    requestId,
    actorType: input.actorType ?? "service",
    actorId: normalizeActorId(input.actorId ?? actorId),
    requestedBy: normalizeActorId(input.requestedBy ?? input.actorId ?? actorId),
    observedAt: input.observedAt ?? now
  };
}

function normalizeActorId(value) {
  const text = String(value ?? "");
  return /^[a-z][a-z0-9]*_/.test(text) ? text : makeId("svc", text || "hwlab-sim");
}

export function createAuditEvent({
  serviceId,
  action,
  targetType,
  targetId,
  projectId,
  gatewaySessionId,
  operationId,
  traceId,
  actorType = "service",
  actorId = `svc_${serviceId}`,
  outcome,
  reason,
  metadata = {},
  environment = ENVIRONMENT_DEV,
  occurredAt = isoNow()
}) {
  return {
    auditId: makeId("aud", `${serviceId}_${operationId ?? traceId ?? randomUUID()}_${action}`),
    traceId,
    actorType,
    actorId,
    action,
    targetType,
    targetId,
    ...(projectId ? { projectId } : {}),
    ...(gatewaySessionId ? { gatewaySessionId } : {}),
    ...(operationId ? { operationId } : {}),
    serviceId,
    environment: normalizeEnvironment(environment),
    ...(outcome ? { outcome } : {}),
    ...(reason ? { reason } : {}),
    metadata,
    occurredAt
  };
}

export function createEvidenceRecord({
  serviceId,
  projectId,
  operationId,
  traceId,
  kind = "measurement",
  payload = {},
  metadata = {},
  environment = ENVIRONMENT_DEV,
  createdAt = isoNow()
}) {
  const normalizedEnvironment = normalizeEnvironment(environment);
  const serialized = JSON.stringify({
    serviceId,
    projectId,
    operationId,
    traceId,
    kind,
    environment: normalizedEnvironment,
    payload,
    metadata,
    createdAt
  });
  const sha256 = createHash("sha256").update(serialized).digest("hex");

  return {
    evidenceId: makeId("evd", `${serviceId}_${operationId}_${kind}`),
    projectId,
    operationId,
    kind,
    uri: `memory://${serviceId}/${operationId}/${kind}.json`,
    mimeType: "application/json",
    sha256,
    sizeBytes: Buffer.byteLength(serialized),
    serviceId,
    environment: normalizedEnvironment,
    metadata: {
      traceId,
      ...metadata
    },
    createdAt
  };
}

function normalizeEnvironment(environment) {
  return isProtocolEnvironment(environment) ? environment : ENVIRONMENT_DEV;
}

export function normalizePortValue(value, definition) {
  if (!definition) {
    return value;
  }

  if (definition.valueType === "boolean") {
    if (typeof value === "boolean") {
      return value;
    }
    if (value === "true") {
      return true;
    }
    if (value === "false") {
      return false;
    }
    throw createRuntimeError({
      code: ERROR_CODES.invalidParams,
      message: `${definition.port} requires a boolean value`,
      data: {
        port: definition.port,
        valueType: definition.valueType
      }
    });
  }

  if (definition.valueType === "number") {
    const number = typeof value === "number" ? value : Number(value);
    if (Number.isFinite(number)) {
      return number;
    }
    throw createRuntimeError({
      code: ERROR_CODES.invalidParams,
      message: `${definition.port} requires a finite number value`,
      data: {
        port: definition.port,
        valueType: definition.valueType
      }
    });
  }

  if (definition.valueType === "string" && value !== null && value !== undefined) {
    return String(value);
  }

  if (definition.valueType === "object" && value !== null && value !== undefined) {
    if (typeof value === "object" && !Array.isArray(value)) {
      return value;
    }
    throw createRuntimeError({
      code: ERROR_CODES.invalidParams,
      message: `${definition.port} requires an object value`,
      data: {
        port: definition.port,
        valueType: definition.valueType
      }
    });
  }

  return value ?? null;
}

export function findCapability(state, capabilityId) {
  if (!capabilityId) {
    return null;
  }
  return state.capabilities.find((capability) => capability.capabilityId === capabilityId) ?? null;
}

export function resolvePortForRead(state, { port, capabilityId }) {
  if (capabilityId) {
    const capability = findCapability(state, capabilityId);
    if (!capability?.constraints?.port || !state.ports[capability.constraints.port]) {
      throw createRuntimeError({
        code: ERROR_CODES.capabilityUnavailable,
        message: "Requested hardware capability is unavailable",
        data: {
          capabilityId,
          port: port ?? null,
          resourceId: state.resource.resourceId,
          reason: "capability_unavailable"
        }
      });
    }
    if (port && port !== capability.constraints.port) {
      throw createRuntimeError({
        code: ERROR_CODES.invalidParams,
        message: "Port does not match requested capability",
        data: {
          capabilityId,
          capabilityPort: capability.constraints.port,
          port,
          resourceId: state.resource.resourceId,
          reason: "capability_port_mismatch"
        }
      });
    }

    return capability.constraints.port;
  }

  if (port && state.ports[port]) {
    return port;
  }

  throw createRuntimeError({
    code: ERROR_CODES.capabilityUnavailable,
    message: "Requested hardware capability is unavailable",
    data: {
      capabilityId: capabilityId ?? null,
      port: port ?? null,
      resourceId: state.resource.resourceId,
      reason: "capability_unavailable"
    }
  });
}

export function resolvePortForWrite(state, { port, capabilityId }) {
  const resolvedPort = resolvePortForRead(state, { port, capabilityId });
  const capability = capabilityId ? findCapability(state, capabilityId) : null;
  if (capabilityId && !capability?.mutatesState) {
    throw createRuntimeError({
      code: ERROR_CODES.operationRejected,
      message: "Read-only capability cannot be used for a write operation",
      data: {
        capabilityId,
        port: resolvedPort,
        reason: "read_only_capability"
      }
    });
  }

  return resolvedPort;
}

export function readBoxPort({ state, port, capabilityId, context = operationContext({}) }) {
  const resolvedPort = resolvePortForRead(state, { port, capabilityId });
  const current = state.ports[resolvedPort];
  const capability = capabilityId ? findCapability(state, capabilityId) : null;
  const audit = createAuditEvent({
    serviceId: "hwlab-box-simu",
    action: "hardware.operation.succeeded",
    targetType: "box_resource",
    targetId: state.resource.resourceId,
    projectId: state.projectId,
    gatewaySessionId: state.gatewaySessionId,
    operationId: context.operationId,
    traceId: context.traceId,
    actorType: context.actorType,
    actorId: context.actorId,
    outcome: "succeeded",
    metadata: {
      boxId: state.boxId,
      port: resolvedPort,
      direction: current.direction,
      capabilityId: capability?.capabilityId ?? null,
      operation: "port.read"
    },
    occurredAt: context.observedAt
  });
  const evidence = createEvidenceRecord({
    serviceId: "hwlab-box-simu",
    projectId: state.projectId,
    operationId: context.operationId,
    traceId: context.traceId,
    payload: current,
    metadata: {
      boxId: state.boxId,
      resourceId: state.resource.resourceId,
      capabilityId: capability?.capabilityId ?? null,
      port: resolvedPort
    },
    createdAt: context.observedAt
  });

  return {
    accepted: true,
    operationId: context.operationId,
    traceId: context.traceId,
    boxId: state.boxId,
    resourceId: state.resource.resourceId,
    capabilityId: capability?.capabilityId ?? null,
    port: resolvedPort,
    value: current.value,
    state: current,
    auditId: audit.auditId,
    evidenceId: evidence.evidenceId,
    audit,
    evidence
  };
}

export function writeBoxPort({
  state,
  port,
  capabilityId,
  value,
  source = "local",
  sourceResourceId,
  sourcePort,
  propagatedBy,
  internal = false,
  context = operationContext({})
}) {
  const resolvedPort = resolvePortForWrite(state, { port, capabilityId });
  const definition = getPortDefinition(resolvedPort);
  const current = state.ports[resolvedPort];
  const normalizedValue = normalizePortValue(value, definition);
  const now = context.observedAt;
  const capability = capabilityId ? findCapability(state, capabilityId) : null;

  state.ports[resolvedPort] = {
    ...current,
    value: normalizedValue,
    source,
    ...(sourceResourceId ? { sourceResourceId } : {}),
    ...(sourcePort ? { sourcePort } : {}),
    ...(propagatedBy ? { propagatedBy } : {}),
    updatedAt: now
  };
  state.updatedAt = now;

  const audit = createAuditEvent({
    serviceId: "hwlab-box-simu",
    action: internal ? "hardware.internal_port.written" : "hardware.operation.succeeded",
    targetType: "box_resource",
    targetId: state.resource.resourceId,
    projectId: state.projectId,
    gatewaySessionId: state.gatewaySessionId,
    operationId: context.operationId,
    traceId: context.traceId,
    actorType: context.actorType,
    actorId: context.actorId,
    outcome: "succeeded",
    metadata: {
      boxId: state.boxId,
      port: resolvedPort,
      direction: current.direction,
      capabilityId: capability?.capabilityId ?? null,
      internalPortApi: internal,
      patchPanelOnlyPropagation: true,
      localLoopbackEnabled: false
    },
    occurredAt: now
  });
  const evidence = createEvidenceRecord({
    serviceId: "hwlab-box-simu",
    projectId: state.projectId,
    operationId: context.operationId,
    traceId: context.traceId,
    payload: state.ports[resolvedPort],
    metadata: {
      boxId: state.boxId,
      resourceId: state.resource.resourceId,
      capabilityId: capability?.capabilityId ?? null,
      port: resolvedPort,
      internalPortApi: internal,
      producerServiceId: "hwlab-box-simu"
    },
    createdAt: now
  });

  return {
    accepted: true,
    operationId: context.operationId,
    traceId: context.traceId,
    boxId: state.boxId,
    resourceId: state.resource.resourceId,
    capabilityId: capability?.capabilityId ?? null,
    port: resolvedPort,
    value: normalizedValue,
    state: state.ports[resolvedPort],
    crossDevicePropagation: "patch-panel-only",
    localLoopbackEnabled: false,
    auditId: audit.auditId,
    evidenceId: evidence.evidenceId,
    audit,
    evidence
  };
}

export function handleBoxInvoke({ state, body, context = operationContext(body ?? {}) }) {
  const operation = body.operation ?? body.method;
  if (operation === "port.read" || operation === "hardware.port.read") {
    return readBoxPort({
      state,
      port: body.port,
      capabilityId: body.capabilityId,
      context
    });
  }

  if (operation === "port.write" || operation === "hardware.port.write") {
    return writeBoxPort({
      state,
      port: body.port,
      capabilityId: body.capabilityId,
      value: body.value,
      source: body.source ?? "gateway",
      context
    });
  }

  throw createRuntimeError({
    code: ERROR_CODES.methodNotFound,
    message: "Box simulator invoke method is not implemented",
    data: {
      method: operation ?? null,
      resourceId: state.resource.resourceId
    }
  });
}

export function createRuntimeError({ code, message, data = {}, statusCode }) {
  const error = new Error(message);
  error.statusCode = statusCode ?? httpStatusForErrorCode(code);
  error.body = createRpcErrorBody({ code, message, data });
  return error;
}

export function httpStatusForErrorCode(code) {
  if (code === ERROR_CODES.invalidRequest || code === ERROR_CODES.invalidParams) {
    return 400;
  }
  if (code === ERROR_CODES.methodNotFound || code === ERROR_CODES.capabilityUnavailable) {
    return 404;
  }
  if (code === ERROR_CODES.resourceLocked || code === ERROR_CODES.operationRejected) {
    return 409;
  }
  return 500;
}
