import fs from 'node:fs/promises'
import { createHash } from 'node:crypto'
import { buildHubLauncherAdminPlan } from './adapters/hub-launcher-admin.mjs'
import { buildOpsProductPlan } from './adapters/ops-product-api.mjs'
import { resolveAssistantAction } from './assistant-action-resolver.mjs'
import { validateCatalog } from './validate.mjs'

const WRITE_METHODS = new Set(['PATCH', 'POST', 'PUT'])
const HUB_LAUNCHER_ADMIN_ADAPTER_ID = 'adapter.hub.launcher_admin_api'
const OPS_PRODUCT_API_ADAPTER_ID = 'adapter.ops.product_api'
const ASSISTANT_GATEWAY_NAME = 'ontology-core'
const CONFIRMATION_ENVELOPE_VERSION = 'assistant-write-confirmation-v1'
const ALLOWED_ROUTES = [
  { method: 'GET', pattern: /^\/api\/admin\/control-plane$/ },
  { method: 'PATCH', pattern: /^\/api\/admin\/users\/[^/?#]+\/profile$/ },
  { method: 'PATCH', pattern: /^\/api\/admin\/memberships\/[^/?#]+$/ },
]
const OPS_ALLOWED_ROUTES = [
  { method: 'GET', pattern: /^\/api\/v1\/tools\/issues$/ },
  { method: 'POST', pattern: /^\/api\/v1\/tools\/issues$/ },
  { method: 'POST', pattern: /^\/api\/v1\/tools\/issues\/[^/?#]+\/comments$/ },
]

function headerValue(headers, key) {
  const wanted = key.toLowerCase()
  for (const [candidateKey, value] of Object.entries(headers || {})) {
    if (candidateKey.toLowerCase() === wanted) return value
  }
  return null
}

function normalizeMethod(method) {
  return String(method || '').trim().toUpperCase()
}

function isAllowedRoute(method, path) {
  return ALLOWED_ROUTES.some((route) => route.method === method && route.pattern.test(path))
}

function isAllowedOpsRoute(method, path) {
  return OPS_ALLOWED_ROUTES.some((route) => route.method === method && route.pattern.test(path))
}

function appendQuery(url, query = {}) {
  for (const [key, value] of Object.entries(query || {})) {
    if (value === null || value === undefined || value === '') continue
    url.searchParams.set(key, String(value))
  }
}

function buildRequestUrl(baseUrl, request) {
  if (!baseUrl) throw new Error('baseUrl is required for network execution')
  const url = new URL(request.path, baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`)
  appendQuery(url, request.query)
  return url
}

function readResponseHeader(headers, key) {
  if (!headers) return ''
  if (typeof headers.get === 'function') return headers.get(key) || ''
  return headerValue(headers, key) || ''
}

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

function stableJson(value) {
  if (Array.isArray(value)) {
    return `[${value.map((item) => stableJson(item)).join(',')}]`
  }

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

  return JSON.stringify(value)
}

function confirmationDigest(payload) {
  return createHash('sha256').update(stableJson(payload)).digest('hex').slice(0, 24)
}

function buildWriteConfirmationEnvelope(plan) {
  const request = plan?.request || {}
  const method = normalizeMethod(request.method)
  if (!WRITE_METHODS.has(method)) return null

  const action = plan?.actionDecision?.action || {}
  const actor = plan?.gatewayActor || {}
  const payload = {
    version: CONFIRMATION_ENVELOPE_VERSION,
    actionId: action.id || null,
    app: action.app || null,
    adapterId: plan?.adapterId || null,
    actor: {
      userId: actor.userId || null,
      email: actor.email || null,
      subject: actor.subject || null,
    },
    request: {
      method,
      path: String(request.path || ''),
      body: request.body ?? null,
      idempotencyKey: headerValue(request.headers, 'Idempotency-Key') || null,
    },
  }

  return {
    ...payload,
    token: confirmationDigest(payload),
    riskLevel: action.riskLevel || null,
    confirmationMode: action.confirmationMode || null,
    expectedEffect: action.summary || request.summary || null,
    summary: request.summary || null,
  }
}

function readConfirmationToken(input = {}, options = {}) {
  return normalizeHeaderString(
    options.confirmationToken ||
      input.confirmationToken ||
      input.confirmation?.token ||
      input.confirmationEnvelope?.token,
  )
}

function verifyWriteConfirmationEnvelope(input, options, envelope) {
  if (!envelope) return { ok: true }

  const providedToken = readConfirmationToken(input, options)
  if (!providedToken) {
    return {
      ok: false,
      reason: 'write_confirmation_envelope_missing',
    }
  }

  if (providedToken !== envelope.token) {
    return {
      ok: false,
      reason: 'write_confirmation_envelope_mismatch',
    }
  }

  return { ok: true }
}

function buildBearerHeader(token) {
  const value = normalizeHeaderString(token)
  if (!value) return ''
  return /^Bearer\s+/i.test(value) ? value : `Bearer ${value}`
}

function buildHubLauncherGatewayHeaders(plan, options = {}) {
  if (plan?.adapterId !== HUB_LAUNCHER_ADMIN_ADAPTER_ID) {
    return { ok: true, headers: {} }
  }

  const token = normalizeHeaderString(options.launcherInternalToken || options.internalAccessToken)
  if (!token) {
    return {
      ok: false,
      reason: 'launcher_internal_token_missing',
    }
  }

  const actor = options.gatewayActor || plan.gatewayActor || {}
  const actorUserId = normalizeHeaderString(actor.userId)
  const actorEmail = normalizeHeaderString(actor.email)
  const actorSubject = normalizeHeaderString(actor.subject)

  if (!actorUserId && !actorEmail && !actorSubject) {
    return {
      ok: false,
      reason: 'assistant_gateway_actor_missing',
    }
  }

  return {
    ok: true,
    headers: {
      Authorization: buildBearerHeader(token),
      'X-NODEDC-Assistant-Gateway': ASSISTANT_GATEWAY_NAME,
      ...(actorUserId ? { 'X-NODEDC-Assistant-Actor-User-Id': actorUserId } : {}),
      ...(actorEmail ? { 'X-NODEDC-Assistant-Actor-Email': actorEmail } : {}),
      ...(actorSubject ? { 'X-NODEDC-Assistant-Actor-Subject': actorSubject } : {}),
    },
  }
}

function endpointOrigin(value) {
  try {
    return new URL(value).origin
  } catch {
    return ''
  }
}

function firstArray(...values) {
  for (const value of values) {
    if (Array.isArray(value)) return value
  }
  return []
}

function entitlementOpsGrant(payload) {
  const grants = payload?.appGrants || payload?.grant || payload?.appGrant || payload?.entitlement || payload?.entitlements
  if (grants?.ops && typeof grants.ops === 'object') return grants.ops
  if (payload?.ops && typeof payload.ops === 'object') return payload.ops
  return null
}

function authorizationFromOpsGrant(grant) {
  const servers = firstArray(grant?.mcpServers, grant?.mcp_servers)
  for (const server of servers) {
    const headers = server?.httpHeaders || server?.headers || {}
    const authorization = normalizeHeaderString(headerValue(headers, 'Authorization'))
    if (authorization) return authorization
  }
  return ''
}

function omitEmptyObject(value = {}) {
  const out = {}
  for (const [key, raw] of Object.entries(value || {})) {
    const text = normalizeHeaderString(raw)
    if (text) out[key] = text
  }
  return out
}

async function resolveOpsAuthorization(plan, options = {}) {
  const explicit = buildBearerHeader(options.opsRunToken || options.opsGatewayToken)
  if (explicit) return { ok: true, authorization: explicit }

  const entitlementUrl = normalizeHeaderString(options.opsEntitlementUrl)
  const entitlementAuthorization = normalizeHeaderString(
    options.opsEntitlementAuthorization || buildBearerHeader(options.opsEntitlementToken),
  )
  if (!entitlementUrl || !entitlementAuthorization) {
    return {
      ok: false,
      reason: 'ops_entitlement_not_configured',
    }
  }

  const actor = options.gatewayActor || plan.gatewayActor || {}
  const opsContext = plan.opsContext || {}
  const fetchImpl = options.fetchImpl || globalThis.fetch
  if (typeof fetchImpl !== 'function') {
    throw new Error('No fetch implementation available')
  }

  const owner = omitEmptyObject({
    userId: actor.userId,
    email: actor.email,
    ownerKey: actor.subject,
  })
  const ops = omitEmptyObject({
    workspaceSlug: opsContext.workspaceSlug,
    projectId: opsContext.projectId,
  })

  const response = await fetchImpl(entitlementUrl, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
      Authorization: entitlementAuthorization,
    },
    body: JSON.stringify({
      schemaVersion: 'ai-workspace.entitlement-request.v1',
      appId: 'ops',
      owner,
      runContext: {
        ...(Object.keys(ops).length ? { ops } : {}),
        ...omitEmptyObject({
          opsWorkspaceSlug: opsContext.workspaceSlug,
          opsProjectId: opsContext.projectId,
        }),
      },
      activeContext: {},
      requestedAt: new Date().toISOString(),
    }),
  })
  const payload = await readResponsePayload(response)
  if (!response.ok || payload?.ok === false) {
    return {
      ok: false,
      reason: payload?.error || payload?.message || `ops_entitlement_http_${response.status}`,
    }
  }

  const grant = entitlementOpsGrant(payload)
  const authorization = authorizationFromOpsGrant(grant)
  if (!authorization) {
    return {
      ok: false,
      reason: grant?.status || 'ops_entitlement_token_missing',
      grantStatus: grant?.status || null,
      grantContext: grant?.context || null,
    }
  }

  return { ok: true, authorization }
}

async function buildGatewayHeaders(plan, options = {}) {
  if (plan?.adapterId === HUB_LAUNCHER_ADMIN_ADAPTER_ID) {
    return buildHubLauncherGatewayHeaders(plan, options)
  }

  if (plan?.adapterId === OPS_PRODUCT_API_ADAPTER_ID) {
    const resolved = await resolveOpsAuthorization(plan, options)
    if (!resolved.ok) return resolved
    return {
      ok: true,
      headers: {
        Authorization: resolved.authorization,
        Accept: 'application/json',
      },
    }
  }

  return { ok: true, headers: {} }
}

async function readResponsePayload(response) {
  const raw = typeof response.text === 'function' ? await response.text() : ''
  const contentType = readResponseHeader(response.headers, 'content-type')
  if (!raw) return null
  if (contentType.includes('application/json')) {
    try {
      return JSON.parse(raw)
    } catch {
      return raw
    }
  }
  return raw
}

function planActionId(plan) {
  return String(plan?.actionDecision?.action?.id || '').trim()
}

function dataArray(payload, key) {
  const value = payload?.data?.[key]
  return Array.isArray(value) ? value : []
}

function isPendingLikeStatus(value) {
  const status = String(value || '').trim().toLowerCase()
  return ['new', 'pending', 'requested', 'open', 'waiting', 'submitted'].includes(status)
}

function compactAccessRequest(item = {}) {
  return {
    id: item.id || null,
    email: item.email || null,
    name: [item.lastName, item.firstName, item.middleName].filter(Boolean).join(' ').trim() || null,
    company: item.company || null,
    phone: item.phone || null,
    status: item.status || null,
    targetClientId: item.targetClientId || null,
    role: item.role || null,
    createdAt: item.createdAt || null,
    updatedAt: item.updatedAt || null,
  }
}

function compactInvite(item = {}) {
  return {
    id: item.id || null,
    email: item.email || item.inviteeEmail || null,
    status: item.status || null,
    clientId: item.clientId || item.targetClientId || null,
    role: item.role || null,
    createdAt: item.createdAt || null,
    updatedAt: item.updatedAt || null,
  }
}

function compactUser(item = {}) {
  return {
    id: item.id || null,
    name: item.name || null,
    email: item.email || null,
    globalStatus: item.globalStatus || null,
  }
}

function compactMembership(item = {}) {
  return {
    id: item.id || null,
    clientId: item.clientId || null,
    userId: item.userId || null,
    role: item.role || null,
    status: item.status || null,
    coreAssistantRole: item.coreAssistantRole || null,
  }
}

function firstIssueArray(payload) {
  return firstArray(
    payload?.issues,
    payload?.items,
    payload?.results,
    payload?.data?.issues,
    payload?.data?.items,
    payload?.data?.results,
    Array.isArray(payload?.data) ? payload.data : null,
  )
}

function firstObject(...values) {
  for (const value of values) {
    if (value && typeof value === 'object' && !Array.isArray(value)) return value
  }
  return null
}

function issueTimestamp(item = {}) {
  const value = item.updatedAt || item.updated_at || item.createdAt || item.created_at || item.created || item.updated
  const time = Date.parse(value)
  return Number.isFinite(time) ? time : 0
}

function compactIssue(item = {}) {
  const state = item.state && typeof item.state === 'object' ? item.state : null
  const project = item.project && typeof item.project === 'object' ? item.project : null
  return {
    id: item.id || item.issue_id || null,
    identifier: item.identifier || item.sequence_id || item.issueIdentifier || item.issue_identifier || null,
    title: item.title || item.name || item.subject || null,
    description: item.description || null,
    state: state?.name || item.stateName || item.state_name || item.state || null,
    priority: item.priority || null,
    projectId: item.project_id || item.projectId || project?.id || null,
    projectName: item.project_name || item.projectName || project?.name || null,
    createdAt: item.createdAt || item.created_at || item.created || null,
    updatedAt: item.updatedAt || item.updated_at || item.updated || null,
    url: item.url || item.web_url || item.webUrl || null,
  }
}

function compactComment(item = {}) {
  return {
    id: item.id || item.comment_id || item.commentId || null,
    issueId: item.issue_id || item.issueId || item.issue?.id || null,
    body: item.body || item.comment || item.text || null,
    createdAt: item.createdAt || item.created_at || item.created || null,
    updatedAt: item.updatedAt || item.updated_at || item.updated || null,
  }
}

function normalizeOpsPayload(plan, payload) {
  if (!payload || typeof payload !== 'object') return payload
  const actionId = planActionId(plan)
  if (actionId === 'ops.card.create') {
    const issue = firstObject(
      payload.issue,
      payload.card,
      payload.data?.issue,
      payload.data?.card,
      payload.data,
    )
    return {
      card: issue ? compactIssue(issue) : null,
      source: 'ops-agent-gateway',
      context: plan.opsContext || null,
      rawOk: payload.ok ?? null,
    }
  }
  if (actionId === 'ops.card.add_comment') {
    const comment = firstObject(
      payload.comment,
      payload.issueComment,
      payload.data?.comment,
      payload.data?.issueComment,
      payload.data,
    )
    return {
      comment: comment ? compactComment(comment) : null,
      source: 'ops-agent-gateway',
      context: plan.opsContext || null,
      rawOk: payload.ok ?? null,
    }
  }
  if (actionId !== 'ops.card.list_recent') return payload

  const limit = Number(plan?.opsContext?.limit || 5)
  const issues = firstIssueArray(payload)
    .slice()
    .sort((a, b) => issueTimestamp(b) - issueTimestamp(a))
    .slice(0, Math.max(1, Math.min(20, Number.isFinite(limit) ? limit : 5)))
    .map(compactIssue)

  return {
    counts: {
      cards: issues.length,
    },
    latestCard: issues[0] || null,
    cards: issues,
    source: 'ops-agent-gateway',
    context: plan.opsContext || null,
  }
}

function normalizeExecutionPayload(plan, payload) {
  if (plan?.adapterId === OPS_PRODUCT_API_ADAPTER_ID) {
    return normalizeOpsPayload(plan, payload)
  }
  return normalizeControlPlanePayload(plan, payload)
}

function normalizeControlPlanePayload(plan, payload) {
  if (!payload || typeof payload !== 'object') return payload
  const actionId = planActionId(plan)
  const counts = payload.counts && typeof payload.counts === 'object' ? payload.counts : {}
  const accessRequests = dataArray(payload, 'accessRequests')
  const pendingAccessRequests = accessRequests.filter((item) => isPendingLikeStatus(item?.status))
  const engineRequests = dataArray(payload, 'engineWorkflowAccessRequests')
  const pendingEngineRequests = engineRequests.filter((item) => isPendingLikeStatus(item?.status))
  const invites = dataArray(payload, 'invites')
  const pendingInvites = invites.filter((item) => isPendingLikeStatus(item?.status))

  if (actionId === 'hub.access_request.list_pending') {
    return {
      actor: payload.actor || null,
      counts: {
        accessRequests: counts.accessRequests ?? accessRequests.length,
        engineWorkflowAccessRequests: counts.engineWorkflowAccessRequests ?? engineRequests.length,
      },
      summary: {
        accessRequests: accessRequests.length,
        pendingAccessRequests: pendingAccessRequests.length,
        engineWorkflowAccessRequests: engineRequests.length,
        pendingEngineWorkflowAccessRequests: pendingEngineRequests.length,
        newAccessRequests: pendingAccessRequests.map((item) => item.email).filter(Boolean),
      },
      accessRequests: pendingAccessRequests.map(compactAccessRequest),
      engineWorkflowAccessRequests: pendingEngineRequests.map(compactAccessRequest),
    }
  }

  if (actionId === 'hub.invite.list_pending') {
    return {
      actor: payload.actor || null,
      counts: {
        invites: counts.invites ?? invites.length,
      },
      summary: {
        invites: invites.length,
        pendingInvites: pendingInvites.length,
        pendingInviteEmails: pendingInvites.map((item) => item.email || item.inviteeEmail).filter(Boolean),
      },
      invites: pendingInvites.map(compactInvite),
    }
  }

  if (actionId === 'hub.user.read_admin_summary') {
    return {
      actor: payload.actor || null,
      counts,
      summary: {
        users: counts.users ?? dataArray(payload, 'users').length,
        memberships: counts.memberships ?? dataArray(payload, 'memberships').length,
        services: counts.services ?? dataArray(payload, 'services').length,
        grants: counts.grants ?? dataArray(payload, 'grants').length,
        invites: counts.invites ?? invites.length,
        accessRequests: counts.accessRequests ?? accessRequests.length,
        pendingAccessRequests: pendingAccessRequests.length,
        newAccessRequests: pendingAccessRequests.map((item) => item.email).filter(Boolean),
      },
      users: dataArray(payload, 'users').map(compactUser),
      memberships: dataArray(payload, 'memberships').map(compactMembership),
      accessRequests: pendingAccessRequests.map(compactAccessRequest),
    }
  }

  return payload
}

export function validateExecutionPlan(plan) {
  const errors = []
  const warnings = []
  const request = plan?.request || {}
  const method = normalizeMethod(request.method)
  const path = String(request.path || '')
  const adapterId = plan?.adapterId || plan?.actionDecision?.action?.adapterId || ''

  if (!plan?.ok) {
    errors.push('plan_not_ok')
  }
  if (!method) {
    errors.push('request_method_missing')
  }
  if (method === 'DELETE') {
    errors.push('delete_method_forbidden')
  }

  if (adapterId === OPS_PRODUCT_API_ADAPTER_ID) {
    if (!path.startsWith('/api/v1/tools/')) {
      errors.push('request_path_not_ops_tools_api')
    }
    if (!['GET', 'POST'].includes(method)) {
      errors.push(`method_not_allowed:${method || 'empty'}`)
    }
    if (method && path && !isAllowedOpsRoute(method, path)) {
      errors.push(`route_not_allowlisted:${method} ${path}`)
    }
    if (method === 'GET' && !request.query?.project_id) {
      errors.push('ops_project_id_missing')
    }
    if (method === 'POST' && !request.body?.project_id) {
      errors.push('ops_project_id_missing')
    }
  } else {
    if (!path.startsWith('/api/admin/')) {
      errors.push('request_path_not_admin_api')
    }
    if (!['GET', 'PATCH'].includes(method)) {
      errors.push(`method_not_allowed:${method || 'empty'}`)
    }
    if (method && path && !isAllowedRoute(method, path)) {
      errors.push(`route_not_allowlisted:${method} ${path}`)
    }
  }

  if (WRITE_METHODS.has(method) && !headerValue(request.headers, 'Idempotency-Key')) {
    errors.push('write_requires_idempotency_key')
  }
  if (WRITE_METHODS.has(method) && plan?.safety?.confirmationAlreadyChecked !== true) {
    errors.push('write_requires_prior_confirmation_check')
  }
  if (plan?.actionDecision?.action?.riskLevel === 'destructive') {
    errors.push('destructive_action_forbidden')
  }
  if (plan?.actionDecision?.decision === 'forbidden') {
    errors.push('forbidden_action_decision')
  }
  if (plan?.safety?.noDeleteRouteMapped !== true) {
    warnings.push('no_delete_route_mapping_not_asserted')
  }
  if (plan?.safety?.sourceGuardedRoutesOnly !== true) {
    warnings.push('source_guarded_routes_only_not_asserted')
  }

  return {
    ok: errors.length === 0,
    errors,
    warnings,
    method,
    path,
  }
}

async function executeHttpPlan(plan, options) {
  const request = plan.request
  const method = normalizeMethod(request.method)
  const url = buildRequestUrl(options.baseUrl, request)
  const headers = {
    ...(request.headers || {}),
    ...(options.headers || {}),
  }
  const init = {
    method,
    headers,
  }

  if (method !== 'GET' && request.body !== undefined) {
    if (!headerValue(headers, 'Content-Type')) {
      headers['Content-Type'] = 'application/json'
    }
    if (!headerValue(headers, 'Accept')) {
      headers.Accept = 'application/json'
    }
    init.body = JSON.stringify(request.body)
  }

  const fetchImpl = options.fetchImpl || globalThis.fetch
  if (typeof fetchImpl !== 'function') {
    throw new Error('No fetch implementation available')
  }

  const response = await fetchImpl(url, init)
  const payload = normalizeExecutionPayload(plan, await readResponsePayload(response))

  return {
    ok: Boolean(response.ok),
    status: response.status,
    statusText: response.statusText || '',
    url: String(url),
    method,
    payload,
  }
}

async function buildAssistantExecutionPlan(input = {}, options = {}) {
  const decision = options.decision || await resolveAssistantAction(input, options)
  const adapterId = decision?.action?.adapterId

  if (adapterId === OPS_PRODUCT_API_ADAPTER_ID) {
    return buildOpsProductPlan(input, { ...options, decision })
  }

  return buildHubLauncherAdminPlan(input, { ...options, decision })
}

function executionBaseUrlForPlan(plan, options = {}) {
  if (plan?.adapterId === OPS_PRODUCT_API_ADAPTER_ID) {
    return normalizeHeaderString(options.opsGatewayBaseUrl) || endpointOrigin(options.opsEntitlementUrl)
  }
  return normalizeHeaderString(options.baseUrl)
}

export async function executeAssistantAction(input = {}, options = {}) {
  const mode = options.mode || 'dry-run'
  const plan = options.plan || await buildAssistantExecutionPlan(input, options)
  const safety = validateExecutionPlan(plan)
  const confirmationEnvelope = buildWriteConfirmationEnvelope(plan)

  if (!safety.ok) {
    return {
      ok: false,
      decision: 'execution_blocked',
      reason: 'execution_safety_check_failed',
      safety,
      confirmationEnvelope,
      plan,
    }
  }

  if (mode !== 'execute') {
    return {
      ok: true,
      decision: 'dry_run',
      reason: 'network_execution_not_requested',
      safety,
      confirmationEnvelope,
      plan,
      result: null,
    }
  }

  if (!options.allowNetworkExecution) {
    return {
      ok: false,
      decision: 'execution_blocked',
      reason: 'network_execution_not_enabled',
      safety,
      confirmationEnvelope,
      plan,
    }
  }

  if ((plan.dryRunOnly || plan.adapterStatus !== 'implemented') && !options.allowPlannedAdapterExecution) {
    return {
      ok: false,
      decision: 'execution_blocked',
      reason: 'adapter_not_marked_implemented',
      safety,
      confirmationEnvelope,
      plan,
    }
  }

  const baseUrl = executionBaseUrlForPlan(plan, options)
  if (!baseUrl) {
    return {
      ok: false,
      decision: 'execution_blocked',
      reason: plan?.adapterId === OPS_PRODUCT_API_ADAPTER_ID ? 'ops_gateway_base_url_missing' : 'base_url_missing',
      safety,
      confirmationEnvelope,
      plan,
    }
  }

  const confirmationCheck = verifyWriteConfirmationEnvelope(input, options, confirmationEnvelope)
  if (!confirmationCheck.ok) {
    return {
      ok: false,
      decision: 'execution_blocked',
      reason: confirmationCheck.reason,
      safety,
      confirmationEnvelope,
      plan,
    }
  }

  const gatewayHeaders = await buildGatewayHeaders(plan, options)
  if (!gatewayHeaders.ok) {
    return {
      ok: false,
      decision: 'execution_blocked',
      reason: gatewayHeaders.reason,
      safety,
      confirmationEnvelope,
      plan,
    }
  }

  const result = await executeHttpPlan(plan, {
    ...options,
    baseUrl,
    headers: {
      ...(options.headers || {}),
      ...gatewayHeaders.headers,
    },
  })

  return {
    ok: result.ok,
    decision: result.ok ? 'executed' : 'execution_failed',
    safety,
    confirmationEnvelope,
    plan,
    result,
  }
}

async function readInputJson() {
  const inputIndex = process.argv.indexOf('--input-json')
  if (inputIndex >= 0) {
    const inputPath = process.argv[inputIndex + 1]
    if (!inputPath) throw new Error('--input-json requires a path')
    return JSON.parse(await fs.readFile(inputPath, 'utf8'))
  }

  const inlineIndex = process.argv.indexOf('--input')
  if (inlineIndex >= 0) {
    return JSON.parse(process.argv[inlineIndex + 1] || '{}')
  }

  return null
}

function readArg(name) {
  const index = process.argv.indexOf(name)
  if (index === -1) return null
  return process.argv[index + 1] || null
}

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

async function runSmoke() {
  const validation = await validateCatalog()
  assertSmoke(validation.ok, 'catalog must validate before executor smoke')

  const blockInput = {
    actionId: 'hub.user.block',
    assistantRole: 'admin',
    membershipRole: 'client_admin',
    actorUserId: 'user_root',
    targetUserId: 'user_pupa',
    confirmed: true,
    idempotencyKey: 'smoke:execute:block:user_pupa',
  }

  const dryRun = await executeAssistantAction(blockInput)
  const executeWithoutConfirmation = await executeAssistantAction(blockInput, {
    mode: 'execute',
    allowNetworkExecution: true,
    baseUrl: 'http://launcher.local.test',
  })
  const confirmedBlockInput = {
    ...blockInput,
    confirmationToken: dryRun.confirmationEnvelope?.token,
  }
  const executeBlocked = await executeAssistantAction(confirmedBlockInput, {
    mode: 'execute',
    allowNetworkExecution: true,
    baseUrl: 'http://launcher.local.test',
  })

  const mockCalls = []
  const mockFetch = async (url, init) => {
    mockCalls.push({ url: String(url), init })
    return {
      ok: true,
      status: 200,
      statusText: 'OK',
      headers: { get: () => 'application/json' },
      text: async () => JSON.stringify({ ok: true, source: 'mock-launcher' }),
    }
  }

  const mockExecuted = await executeAssistantAction(confirmedBlockInput, {
    mode: 'execute',
    allowNetworkExecution: true,
    baseUrl: 'http://launcher.local.test',
    launcherInternalToken: 'smoke-token',
    fetchImpl: mockFetch,
  })

  const deleteBlocked = await executeAssistantAction({
    actionId: 'hub.user.delete',
    assistantRole: 'admin',
    membershipRole: 'client_admin',
    actorUserId: 'user_root',
    targetUserId: 'user_pupa',
    confirmed: true,
  }, {
    mode: 'execute',
    allowNetworkExecution: true,
    allowPlannedAdapterExecution: true,
    baseUrl: 'http://launcher.local.test',
    fetchImpl: mockFetch,
  })

  const badDeletePlanSafety = validateExecutionPlan({
    ok: true,
    adapterStatus: 'implemented',
    dryRunOnly: false,
    safety: {
      noDeleteRouteMapped: false,
      sourceGuardedRoutesOnly: false,
      confirmationAlreadyChecked: true,
    },
    actionDecision: {
      decision: 'allowed',
      action: { riskLevel: 'privileged' },
    },
    request: {
      method: 'DELETE',
      path: '/api/admin/users/user_pupa',
      headers: { 'Idempotency-Key': 'bad-delete' },
    },
  })

  assertSmoke(dryRun.decision === 'dry_run', 'default executor mode must be dry_run')
  assertSmoke(Boolean(dryRun.confirmationEnvelope?.token), 'dry-run write should return confirmation envelope token')
  assertSmoke(executeWithoutConfirmation.reason === 'write_confirmation_envelope_missing', 'write execution must require confirmation envelope token')
  assertSmoke(executeBlocked.reason === 'launcher_internal_token_missing', 'confirmed implemented gateway adapter must require an internal token')
  assertSmoke(mockExecuted.decision === 'executed', 'mock execution should execute with gateway auth headers')
  assertSmoke(mockCalls.length === 1, 'mock execution should issue exactly one request')
  assertSmoke(mockCalls[0].init.method === 'PATCH', 'mock execution should use PATCH')
  assertSmoke(mockCalls[0].url === 'http://launcher.local.test/api/admin/users/user_pupa/profile', 'mock execution should hit profile route')
  assertSmoke(mockCalls[0].init.headers.Authorization === 'Bearer smoke-token', 'mock execution should send internal bearer token')
  assertSmoke(mockCalls[0].init.headers['X-NODEDC-Assistant-Gateway'] === ASSISTANT_GATEWAY_NAME, 'mock execution should send assistant gateway header')
  assertSmoke(mockCalls[0].init.headers['X-NODEDC-Assistant-Actor-User-Id'] === 'user_root', 'mock execution should send actor user id')
  assertSmoke(deleteBlocked.decision === 'execution_blocked', 'delete action must block before network execution')
  assertSmoke(!badDeletePlanSafety.ok && badDeletePlanSafety.errors.includes('delete_method_forbidden'), 'DELETE plan must fail safety check')

  return {
    ok: true,
    validation: validation.counts,
    cases: {
      dryRun,
      executeWithoutConfirmation,
      executeBlocked,
      mockExecuted,
      deleteBlocked,
      badDeletePlanSafety,
    },
  }
}

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, --input <json>, or --input-json <path>')

    const allowPlannedAdapterExecution =
      process.argv.includes('--allow-planned-adapter-execution') &&
      process.env.NDC_ASSISTANT_EXECUTOR_UNSAFE_DEV === '1'

    const output = await executeAssistantAction(input, {
      mode: process.argv.includes('--execute') ? 'execute' : 'dry-run',
      allowNetworkExecution: process.argv.includes('--execute'),
      allowPlannedAdapterExecution,
      baseUrl: readArg('--base-url') || process.env.NDC_LAUNCHER_BASE_URL || null,
      confirmationToken: readArg('--confirmation-token') || null,
      launcherInternalToken:
        readArg('--launcher-internal-token') ||
        readArg('--internal-token') ||
        process.env.NDC_LAUNCHER_INTERNAL_ACCESS_TOKEN ||
        process.env.NODEDC_INTERNAL_ACCESS_TOKEN ||
        process.env.NODEDC_PLATFORM_SERVICE_TOKEN ||
        null,
    })

    console.log(JSON.stringify(output, null, 2))
  }
}
