import { loadCatalog, readJson } from './catalog.mjs'

const LAUNCHER_ROOT_GROUPS = new Set(['nodedc:superadmin', 'nodedc:launcher:admin'])
const LAUNCHER_ROOT_ROLES = new Set(['root_admin'])
const LAUNCHER_CLIENT_ADMIN_ROLES = new Set(['client_owner', 'client_admin'])

export async function loadAssistantAccessPolicy() {
  const catalog = await loadCatalog()
  return catalog.assistantAccessPolicy
}

function normalizeString(value) {
  return String(value || '').trim()
}

function normalizeRole(value, policy) {
  const raw = normalizeString(value).toLowerCase()
  if (!raw) return ''

  const roleIds = new Set(policy.roleVocabulary.map((role) => role.id))
  if (roleIds.has(raw)) return raw

  const alias = policy.roleAliases.find((candidate) => candidate.alias.toLowerCase() === raw)
  return alias?.roleId || raw
}

function toStringSet(value) {
  if (!Array.isArray(value)) return new Set()
  return new Set(value.map((item) => String(item)))
}

function resolveLauncherAdminScope(input) {
  const groups = toStringSet(input.groups)
  const launcherGlobalRole = normalizeString(input.launcherGlobalRole)
  const membershipRole = normalizeString(input.membershipRole)
  const membershipStatus = normalizeString(input.membershipStatus || 'active')

  const isRoot = Boolean(input.isRoot || input.isSuperAdmin || LAUNCHER_ROOT_ROLES.has(launcherGlobalRole) || [...groups].some((group) => LAUNCHER_ROOT_GROUPS.has(group)))
  const isClientAdmin = membershipStatus === 'active' && LAUNCHER_CLIENT_ADMIN_ROLES.has(membershipRole)

  return {
    isRoot,
    isClientAdmin,
    present: isRoot || isClientAdmin,
    reason: isRoot ? 'launcher_root_scope' : isClientAdmin ? 'launcher_client_admin_scope' : 'no_launcher_admin_scope',
  }
}

function uniqueSorted(values) {
  return [...new Set(values)].sort()
}

export async function resolveAssistantAccess(input, options = {}) {
  const policy = options.policy || await loadAssistantAccessPolicy()
  const launcherUserStatus = normalizeString(input.launcherUserStatus || input.globalStatus || 'active')
  const membershipStatus = normalizeString(input.membershipStatus || 'active')
  const adminScope = resolveLauncherAdminScope(input)

  let requestedRole = normalizeRole(input.assistantRole, policy)
  if (!requestedRole) {
    requestedRole = adminScope.isRoot
      ? policy.defaultResolution.superAdminDefaultRole
      : policy.defaultResolution.activeUserDefaultRole
  }

  const forcedBlocked = launcherUserStatus === 'blocked' || membershipStatus === 'disabled'
  const effectiveRole = forcedBlocked ? policy.defaultResolution.blockedUserEffectiveRole : requestedRole
  const matrix = policy.roleMatrix.find((entry) => entry.roleId === effectiveRole)

  if (!matrix) {
    throw new Error(`Unknown assistant role: ${effectiveRole}`)
  }

  const allCapabilities = policy.capabilities.map((capability) => capability.id)
  let allowedCapabilities = matrix.deniedCapabilities?.includes('*') ? [] : [...(matrix.allowedCapabilities || [])]
  const deniedCapabilities = new Set(matrix.deniedCapabilities?.includes('*') ? allCapabilities : matrix.deniedCapabilities || [])
  const limitations = []

  for (const conditional of matrix.conditionalCapabilities || []) {
    if (conditional.requires === 'launcher_admin_scope.root' && adminScope.isRoot) {
      allowedCapabilities.push(conditional.capabilityId)
      deniedCapabilities.delete(conditional.capabilityId)
    } else {
      deniedCapabilities.add(conditional.capabilityId)
      limitations.push({
        capabilityId: conditional.capabilityId,
        reason: conditional.requires,
      })
    }
  }

  if (effectiveRole === 'assistant_admin' && !adminScope.present) {
    for (const capabilityId of matrix.deniedWhenNoAdminScope || []) {
      deniedCapabilities.add(capabilityId)
      allowedCapabilities = allowedCapabilities.filter((candidate) => candidate !== capabilityId)
    }
    limitations.push({
      reason: 'assistant_admin_requires_existing_launcher_admin_scope',
    })
  }

  if (forcedBlocked) {
    allowedCapabilities = []
    for (const capabilityId of allCapabilities) {
      deniedCapabilities.add(capabilityId)
    }
    limitations.push({
      reason: launcherUserStatus === 'blocked' ? 'launcher_user_blocked' : 'membership_disabled',
    })
  }

  allowedCapabilities = uniqueSorted(allowedCapabilities.filter((capabilityId) => !deniedCapabilities.has(capabilityId)))

  return {
    ok: true,
    requestedRole,
    effectiveRole,
    visible: Boolean(matrix.visible && !forcedBlocked),
    canUse: Boolean(matrix.canUse && !forcedBlocked),
    launcherUserStatus,
    membershipStatus,
    adminScope,
    allowedCapabilities,
    deniedCapabilities: uniqueSorted([...deniedCapabilities]),
    limitations,
    hardDenies: policy.hardDenies.map((deny) => deny.id),
  }
}

async function readInputJson() {
  const inputIndex = process.argv.indexOf('--input-json')
  if (inputIndex === -1) return null
  const inputPath = process.argv[inputIndex + 1]
  if (!inputPath) {
    throw new Error('--input-json requires a path')
  }
  return readJson(inputPath)
}

function assertSmoke(condition, message) {
  if (!condition) {
    throw new Error(message)
  }
}

async function runSmoke() {
  const policy = await loadAssistantAccessPolicy()
  const member = await resolveAssistantAccess({
    assistantRole: 'member',
    launcherUserStatus: 'active',
    membershipRole: 'member',
  }, { policy })

  const clientAdmin = await resolveAssistantAccess({
    assistantRole: 'admin',
    launcherUserStatus: 'active',
    membershipRole: 'client_admin',
    membershipStatus: 'active',
  }, { policy })

  const blocked = await resolveAssistantAccess({
    assistantRole: 'admin',
    launcherUserStatus: 'blocked',
    membershipRole: 'client_admin',
  }, { policy })

  const superAdmin = await resolveAssistantAccess({
    groups: ['nodedc:superadmin'],
    launcherUserStatus: 'active',
  }, { policy })

  assertSmoke(member.visible, 'member assistant should be visible')
  assertSmoke(!member.allowedCapabilities.includes('assistant.manage_users'), 'member assistant must not manage users')
  assertSmoke(clientAdmin.allowedCapabilities.includes('assistant.manage_users'), 'client assistant admin should manage users inside Launcher scope')
  assertSmoke(!clientAdmin.allowedCapabilities.includes('assistant.manage_service_catalog'), 'client assistant admin must not get root service catalog capability')
  assertSmoke(!blocked.visible && !blocked.canUse, 'blocked assistant must be hidden and unusable')
  assertSmoke(superAdmin.effectiveRole === 'assistant_admin', 'superadmin should default to assistant_admin')
  assertSmoke(superAdmin.allowedCapabilities.includes('assistant.manage_service_catalog'), 'superadmin should receive root conditional capabilities')

  return {
    ok: true,
    cases: { member, clientAdmin, blocked, superAdmin },
  }
}

if (import.meta.url === `file://${process.argv[1]}`) {
  if (process.argv.includes('--smoke')) {
    console.log(JSON.stringify(await runSmoke(), null, 2))
  } else {
    const input = await readInputJson()
    if (!input) {
      throw new Error('Use --smoke or --input-json <path>')
    }
    console.log(JSON.stringify(await resolveAssistantAccess(input), null, 2))
  }
}
