'use strict';

const crypto = require('node:crypto');

function normalizePrincipal(input = {}) {
  return {
    channel: input.channel || input.channelId || 'unknown',
    userId: String(input.userId || input.senderId || input.from || ''),
    threadId: String(input.threadId || input.conversationId || input.roomId || ''),
    groupId: String(input.groupId || input.roomId || ''),
    isGroup: Boolean(input.isGroup || input.groupId || input.roomId),
    command: input.command || '',
  };
}

function makePrincipalKey(principal = {}) {
  const p = normalizePrincipal(principal);
  return [p.channel, p.isGroup ? 'group' : 'dm', p.groupId || p.threadId || p.userId].join(':');
}

function evaluateDirectMessageAccess(input = {}) {
  const principal = normalizePrincipal(input);
  const policy = input.policy || {};
  if (policy.enabled === false) return decision('block', 'dm-policy-disabled', principal);
  const allowlist = normalizeList(policy.allowUsers || policy.allowlist || []);
  const pairedUsers = new Set(normalizeList(input.pairedUsers || []));
  if (allowlist.length === 0 && pairedUsers.size === 0) return decision('allow', 'dm-open', principal);
  if (allowlist.includes(principal.userId)) return decision('allow', 'dm-user-allowlisted', principal);
  if (pairedUsers.has(principal.userId)) return decision('allow', 'dm-user-paired', principal);
  if (policy.pairingRequired) return decision('pairing_required', 'dm-pairing-required', principal);
  return decision('block', 'dm-user-not-allowlisted', principal);
}

function evaluateGroupAccess(input = {}) {
  const principal = normalizePrincipal({ ...input, isGroup: true });
  const policy = input.policy || {};
  if (policy.enabled === false) return decision('block', 'group-policy-disabled', principal);
  const allowGroups = normalizeList(policy.allowGroups || policy.groupAllowlist || []);
  const allowUsers = normalizeList(policy.allowUsers || policy.senderAllowlist || []);
  if (allowGroups.length === 0 && allowUsers.length === 0) {
    return decision(policy.defaultOpen === false ? 'block' : 'allow', policy.defaultOpen === false ? 'group-default-closed' : 'group-open', principal);
  }
  if (allowGroups.includes(principal.groupId || principal.threadId)) return decision('allow', 'group-allowlisted', principal);
  if (allowUsers.includes(principal.userId)) return decision('allow', 'group-sender-allowlisted', principal);
  return decision('block', 'group-not-allowlisted', principal);
}

function createPairingChallenge({ channel, userId, ttlMs = 5 * 60 * 1000, now = Date.now } = {}) {
  const nonce = crypto.randomBytes(8).toString('hex');
  return {
    channel: channel || 'unknown',
    userId: String(userId || ''),
    nonce,
    code: nonce.slice(0, 6).toUpperCase(),
    expiresAt: now() + ttlMs,
  };
}

function verifyPairingChallenge(challenge, code, { now = Date.now } = {}) {
  if (!challenge) return { ok: false, reason: 'missing-challenge' };
  if (now() > challenge.expiresAt) return { ok: false, reason: 'challenge-expired' };
  if (String(code || '').trim().toUpperCase() !== challenge.code) return { ok: false, reason: 'code-mismatch' };
  return { ok: true, userId: challenge.userId, channel: challenge.channel };
}

function decision(status, reason, principal) {
  return {
    status,
    allow: status === 'allow',
    reason,
    principal,
  };
}

function normalizeList(values) {
  return (Array.isArray(values) ? values : [values])
    .map(value => String(value || '').trim())
    .filter(Boolean);
}

module.exports = {
  createPairingChallenge,
  evaluateDirectMessageAccess,
  evaluateGroupAccess,
  makePrincipalKey,
  normalizePrincipal,
  verifyPairingChallenge,
};
