import {
  ENVIRONMENT_DEV,
  assertProtocolRecord,
  isFrozenServiceId
} from "../protocol/index.mjs";

export const CLOUD_API_SERVICE_ID = "hwlab-cloud-api";

export const AUDIT_FIELD_NAMES = Object.freeze([
  "requestId",
  "actor",
  "source",
  "operation",
  "target",
  "result",
  "timestamp"
]);

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

export function createAuditRecord(input = {}) {
  const record = {
    requestId: asString(input.requestId, "req_unassigned"),
    actor: normalizeActor(input.actor),
    source: normalizeSource(input.source, input.traceId),
    operation: asString(input.operation, "cloud_api.request"),
    target: normalizeTarget(input.target),
    result: asString(input.result, "unknown"),
    timestamp: asString(input.timestamp, new Date().toISOString())
  };

  assertAuditFields(record);
  return record;
}

export function createProtocolAuditEvent(input = {}) {
  const event = {
    auditId: asString(input.auditId, `aud_${cryptoRandomId()}`),
    traceId: asString(input.traceId, `trc_${cryptoRandomId()}`),
    actorType: normalizeActorType(input.actorType),
    actorId: asString(input.actorId, `system_${CLOUD_API_SERVICE_ID}`),
    action: asString(input.action, "cloud_api.request"),
    targetType: asString(input.targetType, "cloud_api"),
    targetId: asString(input.targetId, `svc_${CLOUD_API_SERVICE_ID}`),
    serviceId: isFrozenServiceId(input.serviceId) ? input.serviceId : CLOUD_API_SERVICE_ID,
    environment: ENVIRONMENT_DEV,
    occurredAt: asString(input.occurredAt, new Date().toISOString())
  };

  copyOptional(event, input, [
    "projectId",
    "gatewaySessionId",
    "workerSessionId",
    "operationId",
    "outcome",
    "reason",
    "metadata"
  ]);

  assertProtocolRecord("auditEvent", event);
  return event;
}

export function deriveActorFromMeta(meta = {}) {
  return {
    type: meta.actorType || "system",
    id: meta.actorId || `system_${CLOUD_API_SERVICE_ID}`
  };
}

export function deriveProtocolActorFromMeta(meta = {}, fallback = {}) {
  return {
    actorType: normalizeActorType(fallback.actorType || meta.actorType || "service"),
    actorId: asString(fallback.actorId || meta.actorId, `system_${CLOUD_API_SERVICE_ID}`)
  };
}

export function assertAuditFields(record) {
  if (!record || typeof record !== "object" || Array.isArray(record)) {
    throw new Error("audit record must be an object");
  }

  for (const field of AUDIT_FIELD_NAMES) {
    if (!Object.hasOwn(record, field)) {
      throw new Error(`audit.${field} is required`);
    }
  }

  if (Number.isNaN(Date.parse(record.timestamp))) {
    throw new Error("audit.timestamp must be an RFC 3339 timestamp");
  }
}

function normalizeActor(actor) {
  if (!actor) {
    return {
      type: "system",
      id: `system_${CLOUD_API_SERVICE_ID}`
    };
  }

  if (typeof actor === "string") {
    return {
      type: "user",
      id: actor
    };
  }

  if (typeof actor === "object" && !Array.isArray(actor)) {
    return {
      type: asString(actor.type, "user"),
      id: asString(actor.id, `system_${CLOUD_API_SERVICE_ID}`)
    };
  }

  return {
    type: "system",
    id: `system_${CLOUD_API_SERVICE_ID}`
  };
}

function normalizeSource(source, traceId) {
  const base = {
    serviceId: CLOUD_API_SERVICE_ID,
    environment: ENVIRONMENT_DEV
  };

  if (traceId) {
    base.traceId = traceId;
  }

  if (!source) {
    return base;
  }

  if (typeof source === "string") {
    return {
      ...base,
      serviceId: source
    };
  }

  if (typeof source === "object" && !Array.isArray(source)) {
    return {
      ...base,
      ...source
    };
  }

  return base;
}

function normalizeTarget(target) {
  if (!target) {
    return {
      type: "cloud_api",
      id: CLOUD_API_SERVICE_ID
    };
  }

  if (typeof target === "string") {
    return {
      type: "resource",
      id: target
    };
  }

  if (typeof target === "object" && !Array.isArray(target)) {
    return {
      type: asString(target.type, "resource"),
      id: asString(target.id, "target_unspecified")
    };
  }

  return {
    type: "cloud_api",
    id: CLOUD_API_SERVICE_ID
  };
}

function asString(value, fallback) {
  if (value === undefined || value === null || value === "") {
    return fallback;
  }
  return String(value);
}

function normalizeActorType(value) {
  const actorType = asString(value, "system");
  if (["user", "service", "agent", "worker", "system"].includes(actorType)) {
    return actorType;
  }
  return "system";
}

function copyOptional(target, source, fields) {
  for (const field of fields) {
    if (source[field] !== undefined && source[field] !== null) {
      target[field] = source[field];
    }
  }
}

function cryptoRandomId() {
  return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(16).slice(2)}`;
}
