import fs from 'node:fs/promises'
import { executeAssistantAction } from './assistant-action-executor.mjs'
import { validateCatalog } from './validate.mjs'

function actionFromResult(result) {
  return result?.plan?.actionDecision?.action || null
}

function actorFromResult(result) {
  return result?.plan?.actionDecision?.actor || null
}

function requestFromResult(result) {
  return result?.plan?.request || null
}

function executionStateFromResult(result) {
  const action = actionFromResult(result)
  const request = requestFromResult(result)
  const confirmationEnvelope = result?.confirmationEnvelope || null

  return {
    adapterId: result?.plan?.adapterId || action?.adapterId || null,
    adapterStatus: result?.plan?.adapterStatus || action?.adapterStatus || null,
    executionReady: Boolean(result?.plan?.executionReady),
    requiresUserConfirmation: Boolean(confirmationEnvelope),
    requiresInternalToken: Boolean(result?.plan?.adapterId === 'adapter.hub.launcher_admin_api'),
    method: request?.method || null,
    path: request?.path || null,
    idempotencyKey: request?.headers?.['Idempotency-Key'] || request?.headers?.['idempotency-key'] || null,
  }
}

function previewFromDryRun(result) {
  const action = actionFromResult(result)
  const actor = actorFromResult(result)
  const request = requestFromResult(result)
  const confirmationEnvelope = result?.confirmationEnvelope || null

  return {
    ok: Boolean(result?.ok),
    phase: 'preview',
    decision: result?.ok ? 'preview_ready' : result?.decision || 'preview_blocked',
    reason: result?.reason || null,
    action,
    actor,
    preview: {
      app: action?.app || null,
      actionId: action?.id || null,
      riskLevel: action?.riskLevel || null,
      confirmationMode: action?.confirmationMode || null,
      expectedEffect: confirmationEnvelope?.expectedEffect || action?.summary || request?.summary || null,
      request: request ? {
        method: request.method,
        path: request.path,
        query: request.query || null,
        body: request.body ?? null,
      } : null,
      safeAlternatives: result?.plan?.actionDecision?.safeAlternatives || [],
    },
    confirmation: confirmationEnvelope ? {
      token: confirmationEnvelope.token,
      version: confirmationEnvelope.version,
      expectedEffect: confirmationEnvelope.expectedEffect,
      summary: confirmationEnvelope.summary,
      request: confirmationEnvelope.request,
    } : null,
    execution: executionStateFromResult(result),
    raw: result,
  }
}

function executeResponseFromResult(result) {
  const action = actionFromResult(result)
  const request = requestFromResult(result)

  return {
    ok: Boolean(result?.ok),
    phase: 'execute',
    decision: result?.decision || 'execution_unknown',
    reason: result?.reason || null,
    action,
    execution: executionStateFromResult(result),
    request: request ? {
      method: request.method,
      path: request.path,
      query: request.query || null,
      body: request.body ?? null,
    } : null,
    result: result?.result || null,
    confirmation: result?.confirmationEnvelope ? {
      token: result.confirmationEnvelope.token,
      version: result.confirmationEnvelope.version,
      expectedEffect: result.confirmationEnvelope.expectedEffect,
    } : null,
    raw: result,
  }
}

export async function previewAssistantAction(input = {}, options = {}) {
  // Preview must build the write plan and confirmation envelope without doing I/O.
  // The actual execute phase still requires the returned confirmation token.
  const dryRun = await executeAssistantAction({
    ...input,
    confirmed: true,
  }, {
    ...options,
    planPreview: true,
    mode: 'dry-run',
    allowNetworkExecution: false,
  })
  return previewFromDryRun(dryRun)
}

export async function executeAssistantCallerAction(input = {}, options = {}) {
  const executed = await executeAssistantAction({
    ...input,
    confirmed: true,
  }, {
    ...options,
    mode: 'execute',
    allowNetworkExecution: true,
    confirmationToken: options.confirmationToken || input.confirmationToken || input.confirmation?.token || null,
  })
  return executeResponseFromResult(executed)
}

export async function handleAssistantCallerRequest(payload = {}, options = {}) {
  const phase = payload.phase || payload.mode || 'preview'
  const input = payload.input || payload

  if (phase === 'preview' || phase === 'dry-run') {
    return previewAssistantAction(input, options)
  }

  if (phase === 'execute') {
    return executeAssistantCallerAction(input, {
      ...options,
      confirmationToken: payload.confirmationToken || input.confirmationToken || options.confirmationToken || null,
    })
  }

  return {
    ok: false,
    phase,
    decision: 'caller_phase_unknown',
    reason: `Unsupported assistant caller phase: ${phase}`,
  }
}

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 caller smoke')

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

  const preview = await previewAssistantAction(writeInput)
  const executeWithoutToken = await executeAssistantCallerAction(writeInput, {
    baseUrl: 'http://launcher.local.test',
    launcherInternalToken: 'smoke-token',
    fetchImpl: async () => {
      throw new Error('fetch must not run without confirmation token')
    },
  })

  const mockCalls = []
  const executeWithToken = await executeAssistantCallerAction({
    ...writeInput,
    confirmationToken: preview.confirmation?.token,
  }, {
    baseUrl: 'http://launcher.local.test',
    launcherInternalToken: 'smoke-token',
    fetchImpl: 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 readPreview = await previewAssistantAction({
    actionId: 'hub.user.read_admin_summary',
    assistantRole: 'admin',
    groups: ['nodedc:superadmin'],
    actorUserId: 'user_root',
  })

  assertSmoke(preview.decision === 'preview_ready', 'write preview should be ready')
  assertSmoke(Boolean(preview.confirmation?.token), 'write preview should include confirmation token')
  assertSmoke(preview.execution.requiresUserConfirmation, 'write preview should require user confirmation')
  assertSmoke(executeWithoutToken.reason === 'write_confirmation_envelope_missing', 'execute without token must be blocked')
  assertSmoke(executeWithToken.decision === 'executed', 'execute with token should call adapter')
  assertSmoke(mockCalls.length === 1, 'execute with token should issue one request')
  assertSmoke(readPreview.decision === 'preview_ready', 'read preview should be ready')
  assertSmoke(readPreview.confirmation === null, 'read preview should not require confirmation token')

  return {
    ok: true,
    validation: validation.counts,
    cases: {
      preview,
      executeWithoutToken,
      executeWithToken,
      readPreview,
    },
  }
}

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

    const output = await handleAssistantCallerRequest(payload, {
      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))
  }
}
