'use strict';

const DEFAULT_DM_POLICY = 'pairing';

function resolveAgentRoute(input = {}, options = {}) {
  const route = normalizeRouteInput(input, options);
  const binding = chooseBinding(route, options.bindings || []);
  const agentId = binding?.agentId || route.agentId || options.defaultAgentId || 'walle';
  const accessDecision = resolveAccessDecision(route, binding, options);
  const sessionScope = binding?.sessionScope || route.sessionScope || defaultSessionScope(route);
  const sessionKey = route.sessionKey || deriveSessionKey({ ...route, agentId, sessionScope });
  const replyPolicy = resolveReplyPolicy(route, accessDecision, binding, options);

  return {
    routeId: route.routeId || deriveRouteId({ ...route, agentId }),
    agentId,
    source: route.source,
    sourceAccount: route.sourceAccount,
    channelId: route.channelId,
    peerId: route.peerId,
    parentPeerId: route.parentPeerId,
    threadId: route.threadId,
    groupId: route.groupId,
    guildId: route.guildId,
    roleIds: route.roleIds,
    isGroup: route.isGroup,
    sessionKey,
    sessionScope,
    accessDecision,
    replyPolicy,
    reason: binding?.reason || accessDecision.reason || 'default',
    metadata: {
      bindingId: binding?.id || null,
    },
  };
}

function normalizeRouteInput(input = {}, options = {}) {
  const channelId = input.channelId || input.channel || options.channelId || 'ctm';
  const peerId = input.peerId || input.userId || input.senderId || input.peer || '';
  const groupId = input.groupId || input.roomId || input.conversationId || '';
  const threadId = input.threadId || input.parentPeerId || input.ts || groupId || peerId || '';
  const isGroup = input.isGroup ?? Boolean(groupId || input.guildId || input.roomId);
  return {
    routeId: input.routeId || '',
    agentId: input.agentId || input.agent || '',
    source: input.source || input.sourceChannel || channelId,
    sourceAccount: input.sourceAccount || input.accountId || input.workspaceId || '',
    channelId,
    peerId,
    parentPeerId: input.parentPeerId || groupId || '',
    threadId,
    groupId,
    guildId: input.guildId || '',
    roleIds: Array.isArray(input.roleIds) ? input.roleIds.map(String).sort() : [],
    isGroup,
    sessionScope: input.sessionScope || '',
    sessionKey: input.sessionKey || '',
  };
}

function chooseBinding(route, bindings = []) {
  const candidates = [];
  for (const binding of bindings || []) {
    const score = bindingScore(route, binding);
    if (score > 0) candidates.push({ ...binding, score });
  }
  candidates.sort((a, b) => b.score - a.score || String(a.id || '').localeCompare(String(b.id || '')));
  return candidates[0] || null;
}

function bindingScore(route, binding = {}) {
  let score = 0;
  if (binding.channelId || binding.channel) {
    if ((binding.channelId || binding.channel) !== route.channelId) return 0;
    score += 10;
  }
  if (binding.peerId || binding.userId) {
    if ((binding.peerId || binding.userId) !== route.peerId) return 0;
    score += 30;
  }
  if (binding.guildId) {
    if (binding.guildId !== route.guildId) return 0;
    score += 20;
  }
  if (binding.roleIds) {
    const required = new Set(binding.roleIds.map(String));
    const actual = new Set(route.roleIds);
    for (const role of required) {
      if (!actual.has(role)) return 0;
    }
    score += 15 + required.size;
  }
  if (binding.groupId || binding.threadId) {
    const expected = binding.groupId || binding.threadId;
    if (expected !== route.groupId && expected !== route.threadId) return 0;
    score += 25;
  }
  return score || (binding.default ? 1 : 0);
}

function resolveAccessDecision(route, binding = null, options = {}) {
  if (binding?.access === 'deny') return decision(false, 'binding.deny');
  if (binding?.access === 'allow') return decision(true, 'binding.allow');
  if (route.channelId === 'ctm' || route.source === 'ctm') return decision(true, 'source.ctm');
  if (route.isGroup) {
    const groupPolicy = options.groupPolicy || {};
    if (groupPolicy.disabled) return decision(false, 'group.disabled');
    if (groupPolicy.allowedGroupIds && !groupPolicy.allowedGroupIds.includes(route.groupId)) {
      return decision(false, 'group.not_allowlisted');
    }
    return decision(true, groupPolicy.allowedGroupIds ? 'group.allowlist' : 'group.default');
  }

  const dmPolicy = normalizeDmPolicy(options.dmPolicy || options.policy?.dmPolicy || DEFAULT_DM_POLICY);
  if (dmPolicy === 'disabled') return decision(false, 'dm.disabled');
  if (dmPolicy === 'open') return decision(true, 'dm.open');

  const allowlist = options.allowUsers || options.policy?.allowUsers || options.policy?.allowlist || [];
  if (dmPolicy === 'allowlist') {
    return allowlist.includes(route.peerId)
      ? decision(true, 'dm.allowlist')
      : decision(false, 'dm.not_allowlisted');
  }

  const paired = options.pairedUsers || options.policy?.pairedUsers || [];
  if (dmPolicy === 'pairing') {
    return paired.includes(route.peerId)
      ? decision(true, 'dm.paired')
      : decision(false, 'dm.pairing_required');
  }

  return decision(false, 'dm.unknown_policy');
}

function resolveReplyPolicy(route, accessDecision, binding = null, options = {}) {
  if (!accessDecision.allowed) return { canReply: false, mode: 'none', reason: accessDecision.reason };
  if (binding?.replyPolicy) return normalizeReplyPolicy(binding.replyPolicy);
  if (options.replyPolicy) return normalizeReplyPolicy(options.replyPolicy);
  return {
    canReply: true,
    mode: route.isGroup ? 'thread' : 'direct',
    target: route.threadId || route.peerId,
    reason: route.isGroup ? 'group.thread' : 'dm.direct',
  };
}

function normalizeReplyPolicy(policy) {
  if (typeof policy === 'string') return { canReply: policy !== 'none', mode: policy, reason: `policy.${policy}` };
  return {
    canReply: policy.canReply !== false,
    mode: policy.mode || 'direct',
    target: policy.target || '',
    reason: policy.reason || 'policy',
  };
}

function defaultSessionScope(route) {
  if (route.source === 'ctm' || route.channelId === 'ctm') return 'ctm-session';
  if (route.isGroup) return 'per-channel-thread';
  if (route.sourceAccount) return 'per-account-channel-peer';
  return 'per-channel-peer';
}

function deriveSessionKey(route) {
  const scope = route.sessionScope || defaultSessionScope(route);
  if (scope === 'ctm-session') return stableKey(['ctm', route.agentId, route.threadId || route.peerId || route.routeId]);
  if (scope === 'per-channel-thread') return stableKey([route.sourceAccount, route.channelId, route.agentId, route.threadId || route.groupId]);
  if (scope === 'per-account-channel-peer') return stableKey([route.sourceAccount, route.channelId, route.agentId, route.peerId]);
  if (scope === 'per-channel-peer') return stableKey([route.channelId, route.agentId, route.peerId]);
  return stableKey([route.channelId, route.agentId, route.threadId || route.peerId || route.groupId]);
}

function deriveRouteId(route) {
  return stableKey(['route', route.source, route.sourceAccount, route.channelId, route.agentId, route.threadId || route.peerId || route.groupId]);
}

function stableKey(parts = []) {
  return parts
    .filter(part => part !== undefined && part !== null && String(part) !== '')
    .map(part => encodeURIComponent(String(part)))
    .join(':');
}

function normalizeDmPolicy(value) {
  const policy = String(value || DEFAULT_DM_POLICY).toLowerCase();
  return ['pairing', 'allowlist', 'open', 'disabled'].includes(policy) ? policy : DEFAULT_DM_POLICY;
}

function decision(allowed, reason) {
  return { allowed, reason };
}

module.exports = {
  DEFAULT_DM_POLICY,
  deriveRouteId,
  deriveSessionKey,
  normalizeDmPolicy,
  normalizeRouteInput,
  resolveAccessDecision,
  resolveAgentRoute,
};
