'use strict';

const crypto = require('crypto');
const { assertSafe, assertAllowedCanonicalFields } = require('../security/payload-security');
const { enterpriseEntityId } = require('../../../modules/multibranch-sync/domain/entity-identity');

const CONTRACT_VERSION = 'arkhon-mb-sync-v1';

const EVENT_BY_ENTITY = {
  product: 'PRODUCT_UPSERTED',
  product_price: 'PRODUCT_PRICE_UPSERTED',
  supply: 'SUPPLY_ITEM_UPSERTED',
  supply_presentation: 'SUPPLY_PRESENTATION_UPSERTED',
  warehouse: 'WAREHOUSE_UPSERTED',
  inventory_movement: 'INVENTORY_MOVEMENT_RECORDED',
  supplier: 'SUPPLIER_UPSERTED',
  purchase_order: 'PURCHASE_ORDER_UPSERTED',
  purchase: 'PURCHASE_RECORDED',
  transfer: 'TRANSFER_RECORDED',
  recipe: 'RECIPE_UPSERTED',
  requisition: 'REQUISITION_UPSERTED',
  stock_policy: 'STOCK_POLICY_UPSERTED',
  stock_snapshot: 'STOCK_SNAPSHOT_RECORDED',
  operator_user: 'OPERATOR_USER_UPSERTED',
  operator_role: 'OPERATOR_ROLE_UPSERTED',
  operator_permission: 'OPERATOR_PERMISSION_UPSERTED',
  waste_reason: 'WASTE_REASON_UPSERTED',
  inventory_adjustment_reason: 'INVENTORY_ADJUSTMENT_REASON_UPSERTED',
};

const OPERATION_BY_EVENT = Object.freeze({
  PRODUCT_UPSERTED: 'upsert',
  PRODUCT_PRICE_UPSERTED: 'upsert',
  SUPPLY_ITEM_UPSERTED: 'upsert',
  SUPPLY_PRESENTATION_UPSERTED: 'upsert',
  WAREHOUSE_UPSERTED: 'upsert',
  INVENTORY_MOVEMENT_RECORDED: 'record',
  SUPPLIER_UPSERTED: 'upsert',
  PURCHASE_ORDER_UPSERTED: 'upsert',
  PURCHASE_RECORDED: 'record',
  TRANSFER_RECORDED: 'record',
  RECIPE_UPSERTED: 'upsert',
  REQUISITION_UPSERTED: 'upsert',
  STOCK_POLICY_UPSERTED: 'upsert',
  STOCK_SNAPSHOT_RECORDED: 'record',
  OPERATOR_USER_UPSERTED: 'upsert',
  OPERATOR_ROLE_UPSERTED: 'upsert',
  OPERATOR_PERMISSION_UPSERTED: 'upsert',
  OPERATOR_ASSIGNED_TO_BRANCH: 'assign',
  OPERATOR_REMOVED_FROM_BRANCH: 'remove',
  OPERATOR_ACCOUNT_SUSPENDED: 'suspend',
  OPERATOR_ACCOUNT_REACTIVATED: 'reactivate',
  OPERATOR_TERMINATED: 'terminate',
  OPERATOR_PASSWORD_CHANGE_REQUESTED: 'request',
  OPERATOR_PIN_RESET_REQUESTED: 'request',
  WASTE_REASON_UPSERTED: 'upsert',
  WASTE_RECORDED: 'record',
  INVENTORY_ADJUSTMENT_REASON_UPSERTED: 'upsert',
  APPROVAL_REQUESTED: 'request',
  APPROVAL_APPROVED: 'approve',
  APPROVAL_REJECTED: 'reject',
});

function unique(values) {
  return [...new Set(values.filter(Boolean))];
}

function distributionScope(eventType, context, normalized, metadata) {
  let value = context.distributionScope;
  if (!value) {
    if (['PRODUCT_UPSERTED', 'OPERATOR_USER_UPSERTED', 'OPERATOR_ROLE_UPSERTED', 'OPERATOR_PERMISSION_UPSERTED'].includes(eventType)) value = 'tenant';
    else if (['PURCHASE_ORDER_UPSERTED', 'PURCHASE_RECORDED', 'REQUISITION_UPSERTED'].includes(eventType)) value = 'matrix_and_branch';
    else if (eventType === 'TRANSFER_RECORDED') value = 'transfer_participants';
    else if (['OPERATOR_ASSIGNED_TO_BRANCH', 'OPERATOR_REMOVED_FROM_BRANCH'].includes(eventType)) value = 'explicit_branches';
    else if (eventType === 'OPERATOR_TERMINATED') value = 'tenant';
    else if (eventType === 'RECIPE_UPSERTED' && context.recipesGlobal === true) value = 'tenant';
    else if (normalized.metadata?.scope) value = normalized.metadata.scope;
    else value = 'branch';
  }

  const legacyMode = typeof value === 'string' ? value : value.mode;
  const mode = legacyMode === 'matrix_and_branch' ? 'branch' : legacyMode === 'global' ? 'tenant' : legacyMode;
  const objectTargets = typeof value === 'object' && Array.isArray(value.target_branch_ids) ? value.target_branch_ids : [];
  const targetBranchIds = unique([
    ...objectTargets,
    ...(Array.isArray(context.targetBranchIds) ? context.targetBranchIds : []),
    ...(Array.isArray(metadata.target_branch_ids) ? metadata.target_branch_ids : []),
  ]);
  const includeMatrix = typeof value === 'object'
    ? value.include_matrix === true || legacyMode === 'matrix_and_branch'
    : legacyMode === 'matrix_and_branch' || legacyMode === 'transfer_participants';
  return { mode, target_branch_ids: targetBranchIds, include_matrix: includeMatrix };
}

function stableStringify(value) {
  if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
  if (value && typeof value === 'object') {
    return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
  }
  return JSON.stringify(value);
}

function sha256(value) {
  return crypto.createHash('sha256').update(value).digest('hex');
}

function hashUuid(hash) {
  const chars = hash.slice(0, 32).split('');
  chars[12] = '5';
  chars[16] = ((parseInt(chars[16], 16) & 0x3) | 0x8).toString(16);
  const hex = chars.join('');
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}

function defined(value) {
  if (Array.isArray(value)) return value.map(defined);
  if (value && typeof value === 'object') {
    return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined).map(([key, item]) => [key, defined(item)]));
  }
  return value;
}

function buildEvent(eventType, normalized, context) {
  assertAllowedCanonicalFields(eventType, normalized.canonical);
  const contextMetadata = { ...(context.metadata || {}) };
  const branchMap = contextMetadata.branch_map || {};
  delete contextMetadata.branch_map;
  if (eventType === 'TRANSFER_RECORDED') {
    contextMetadata.origin_branch_id = branchMap[normalized.canonical.idempresaorigen] || branchMap[normalized.canonical.almacenorigen];
    contextMetadata.destination_branch_id = branchMap[normalized.canonical.idempresadestino] || branchMap[normalized.canonical.almacendestino];
  }
  if (['OPERATOR_ASSIGNED_TO_BRANCH', 'OPERATOR_REMOVED_FROM_BRANCH'].includes(eventType)) {
    const target = branchMap[normalized.canonical.softrestaurant_branch_id] || normalized.canonical.branch_id;
    contextMetadata.target_branch_ids = target ? [target] : (contextMetadata.target_branch_ids || []);
  }
  const payloadJson = defined({
    canonical: normalized.canonical,
    source: normalized.source,
    metadata: { ...normalized.metadata, ...contextMetadata },
  });
  assertSafe(payloadJson);
  const payloadHash = sha256(stableStringify(payloadJson));
  const sourceUpdatedAt = normalized.source.updated_at || '2000-01-01T00:00:00.000Z';
  const operation = OPERATION_BY_EVENT[eventType];
  if (!operation) throw new Error(`No existe operation para ${eventType}`);
  const scope = distributionScope(eventType, context, normalized, contextMetadata);
  const entityId = enterpriseEntityId(normalized, context);
  const correlationId = context.correlationId || hashUuid(sha256([
    CONTRACT_VERSION, 'correlation', context.tenantId, context.branchId, normalized.entity_type, entityId,
  ].join('|')));
  const causationId = context.causationId || null;
  const eventVersionHash = sha256(stableStringify({
    contract_version: CONTRACT_VERSION,
    tenant_id: context.tenantId,
    source_branch_id: context.branchId,
    source_agent_id: context.agentId,
    source_system: normalized.source.system,
    entity_type: normalized.entity_type,
    entity_id: entityId,
    event_type: eventType,
    operation,
    distribution_scope: scope,
    correlation_id: correlationId,
    causation_id: causationId,
    payload_version: 1,
    payload_hash: payloadHash,
    source_table: normalized.source.table,
    source_id: normalized.source.source_id,
    source_updated_at: sourceUpdatedAt,
  }));
  const eventId = hashUuid(eventVersionHash);
  const idempotencyKey = sha256([
    CONTRACT_VERSION, 'receive', context.tenantId, context.branchId, context.agentId, eventId,
  ].join('|'));
  return {
    contract_version: CONTRACT_VERSION,
    event_id: eventId,
    tenant_id: context.tenantId,
    source_branch_id: context.branchId,
    source_agent_id: context.agentId,
    source_system: normalized.source.system,
    entity_type: normalized.entity_type,
    entity_id: entityId,
    event_type: eventType,
    operation,
    distribution_scope: scope,
    correlation_id: correlationId,
    causation_id: causationId,
    payload_version: 1,
    payload_json: payloadJson,
    payload_hash: payloadHash,
    idempotency_key: idempotencyKey,
    source_table: normalized.source.table,
    source_id: normalized.source.source_id,
    source_updated_at: sourceUpdatedAt,
    created_at: context.createdAt || new Date().toISOString(),
  };
}

function buildForNormalized(normalized, context) {
  const eventType = EVENT_BY_ENTITY[normalized.entity_type];
  if (!eventType) throw new Error(`No existe event builder para ${normalized.entity_type}`);
  return buildEvent(eventType, normalized, context);
}

module.exports = {
  CONTRACT_VERSION,
  EVENT_BY_ENTITY,
  OPERATION_BY_EVENT,
  buildEvent,
  buildForNormalized,
  enterpriseEntityId,
  PRODUCT_UPSERTED: (value, context) => buildEvent('PRODUCT_UPSERTED', value, context),
  PRODUCT_PRICE_UPSERTED: (value, context) => buildEvent('PRODUCT_PRICE_UPSERTED', value, context),
  SUPPLY_ITEM_UPSERTED: (value, context) => buildEvent('SUPPLY_ITEM_UPSERTED', value, context),
  SUPPLY_PRESENTATION_UPSERTED: (value, context) => buildEvent('SUPPLY_PRESENTATION_UPSERTED', value, context),
  WAREHOUSE_UPSERTED: (value, context) => buildEvent('WAREHOUSE_UPSERTED', value, context),
  INVENTORY_MOVEMENT_RECORDED: (value, context) => buildEvent('INVENTORY_MOVEMENT_RECORDED', value, context),
  SUPPLIER_UPSERTED: (value, context) => buildEvent('SUPPLIER_UPSERTED', value, context),
  PURCHASE_ORDER_UPSERTED: (value, context) => buildEvent('PURCHASE_ORDER_UPSERTED', value, context),
  PURCHASE_RECORDED: (value, context) => buildEvent('PURCHASE_RECORDED', value, context),
  TRANSFER_RECORDED: (value, context) => buildEvent('TRANSFER_RECORDED', value, context),
  RECIPE_UPSERTED: (value, context) => buildEvent('RECIPE_UPSERTED', value, context),
  REQUISITION_UPSERTED: (value, context) => buildEvent('REQUISITION_UPSERTED', value, context),
  STOCK_POLICY_UPSERTED: (value, context) => buildEvent('STOCK_POLICY_UPSERTED', value, context),
  STOCK_SNAPSHOT_RECORDED: (value, context) => buildEvent('STOCK_SNAPSHOT_RECORDED', value, context),
  OPERATOR_USER_UPSERTED: (value, context) => buildEvent('OPERATOR_USER_UPSERTED', value, context),
  OPERATOR_ROLE_UPSERTED: (value, context) => buildEvent('OPERATOR_ROLE_UPSERTED', value, context),
  OPERATOR_PERMISSION_UPSERTED: (value, context) => buildEvent('OPERATOR_PERMISSION_UPSERTED', value, context),
  OPERATOR_ASSIGNED_TO_BRANCH: (value, context) => buildEvent('OPERATOR_ASSIGNED_TO_BRANCH', value, context),
  OPERATOR_REMOVED_FROM_BRANCH: (value, context) => buildEvent('OPERATOR_REMOVED_FROM_BRANCH', value, context),
  OPERATOR_ACCOUNT_SUSPENDED: (value, context) => buildEvent('OPERATOR_ACCOUNT_SUSPENDED', value, context),
  OPERATOR_ACCOUNT_REACTIVATED: (value, context) => buildEvent('OPERATOR_ACCOUNT_REACTIVATED', value, context),
  OPERATOR_TERMINATED: (value, context) => buildEvent('OPERATOR_TERMINATED', value, context),
  OPERATOR_PASSWORD_CHANGE_REQUESTED: (value, context) => buildEvent('OPERATOR_PASSWORD_CHANGE_REQUESTED', value, context),
  OPERATOR_PIN_RESET_REQUESTED: (value, context) => buildEvent('OPERATOR_PIN_RESET_REQUESTED', value, context),
  WASTE_REASON_UPSERTED: (value, context) => buildEvent('WASTE_REASON_UPSERTED', value, context),
  WASTE_RECORDED: (value, context) => buildEvent('WASTE_RECORDED', value, context),
  INVENTORY_ADJUSTMENT_REASON_UPSERTED: (value, context) => buildEvent('INVENTORY_ADJUSTMENT_REASON_UPSERTED', value, context),
  APPROVAL_REQUESTED: (value, context) => buildEvent('APPROVAL_REQUESTED', value, context),
  APPROVAL_APPROVED: (value, context) => buildEvent('APPROVAL_APPROVED', value, context),
  APPROVAL_REJECTED: (value, context) => buildEvent('APPROVAL_REJECTED', value, context),
};
