// Action: connect / disconnect / status for Zaprite as the active
// payment provider.
//
// Unlike BTCPay's authorize flow (OAuth-style consent redirect),
// Zaprite doesn't expose a programmatic authorize endpoint. The
// operator creates an API key in their Zaprite dashboard at
// app.zaprite.com/.../settings/api, then pastes it into the form
// here. The daemon validates the key by pinging Zaprite's API,
// then persists + swaps the active provider atomically.
//
// Webhook setup is operator-side: after connecting, the operator
// adds a webhook in Zaprite's dashboard pointing at
// <their-keysat-public-url>/v1/zaprite/webhook. There's no
// signing secret — see the daemon's payment::zaprite module
// comment for the security model (externalUniqId round-trip).

import { sdk } from '../sdk'
import { store } from '../fileModels/store'
import { adminCall, LICENSING_URL } from '../utils'

const { InputSpec, Value } = sdk

const connectInput = InputSpec.of({
  api_key: Value.text({
    name: 'Zaprite API key',
    description:
      'Create an API key at app.zaprite.com → Settings → API. ' +
      'One key per Keysat instance. The key is stored in your ' +
      'StartOS volume (encrypted at rest by StartOS) and never ' +
      'transmitted off your server.',
    required: true,
    default: null,
    masked: true,
  }),
  base_url: Value.text({
    name: 'API base URL',
    description:
      'Defaults to https://api.zaprite.com — only override for ' +
      'sandbox organizations or future regional endpoints.',
    required: false,
    default: 'https://api.zaprite.com',
  }),
})

export const configureZaprite = sdk.Action.withInput(
  'configure-zaprite',
  async () => ({
    name: 'Connect Zaprite',
    description:
      'Connect Keysat to your Zaprite account so buyers can pay with ' +
      'cards (USD/EUR) and Bitcoin via your Zaprite-connected wallets. ' +
      'Use INSTEAD OF Connect BTCPay — only one payment provider can ' +
      'be active at a time. Disconnect first if switching providers.',
    warning:
      'Switching providers does not affect already-issued license keys; ' +
      'they continue to validate normally. New purchases route through ' +
      'whichever provider is active at the time of checkout.',
    allowedStatuses: 'only-running',
    group: 'Zaprite',
    visibility: 'enabled',
  }),
  connectInput,
  // Pre-fill base_url; never pre-fill api_key (force operator to paste fresh).
  async ({ effects: _effects }) => ({
    api_key: '',
    base_url: 'https://api.zaprite.com',
  }),
  async ({ effects: _effects, input }) => {
    const storeData = await store.read().once()
    if (!storeData) throw new Error('Store not initialized — restart the service.')
    const resp = await adminCall(
      LICENSING_URL,
      storeData.admin_api_key,
      '/v1/admin/zaprite/connect',
      {
        method: 'POST',
        body: JSON.stringify({
          api_key: input.api_key.trim(),
          base_url: (input.base_url || 'https://api.zaprite.com').trim(),
        }),
      },
    )
    if (!resp.ok) {
      throw new Error(`Connect failed: HTTP ${resp.status} — ${await resp.text()}`)
    }
    const body = (await resp.json()) as {
      ok: true
      provider: string
      base_url: string
      webhook_url: string
    }
    return {
      version: '1',
      title: 'Zaprite connected',
      message:
        `Active payment provider is now Zaprite (${body.base_url}).\n\n` +
        `Next step — register a webhook in Zaprite's dashboard:\n` +
        `1. Open app.zaprite.com → Settings → Webhooks → New webhook.\n` +
        `2. Paste this URL exactly (full https://, not just the path):\n\n` +
        `   ${body.webhook_url}\n\n` +
        `3. Subscribe to order events (payment_received, settled, refunded).\n\n` +
        `Why webhooks: without them, Keysat falls back to polling Zaprite's ` +
        `/v1/orders every 60 seconds, so license issuance can lag a settle ` +
        `event by up to a minute. With webhooks, Keysat issues the license ` +
        `within ~1 second of payment.\n\n` +
        `Security: Zaprite doesn't sign webhook deliveries. Keysat ` +
        `authenticates each delivery via the externalUniqId we attach at ` +
        `order creation, so a webhook configured to ANY URL on your daemon ` +
        `is safe even without a shared secret.\n\n` +
        `Lost this message? Run "Show Zaprite webhook setup" to see the URL again.`,
      result: null,
    }
  },
)

/**
 * Persistent surface for the webhook URL — operators who skipped the
 * step on first connect, or who just want to verify which URL Zaprite
 * should be posting to, run this and get the exact value to paste.
 */
export const showZapriteWebhookSetup = sdk.Action.withoutInput(
  'show-zaprite-webhook-setup',
  async () => ({
    name: 'Show Zaprite webhook setup',
    description:
      "Display the full webhook URL to register in your Zaprite dashboard, " +
      "plus an explanation of what webhooks do that polling doesn't. " +
      "Useful if you skipped the step on first Connect.",
    warning: null,
    allowedStatuses: 'only-running',
    group: 'Zaprite',
    visibility: 'enabled',
  }),
  async ({ effects: _effects }) => {
    const storeData = await store.read().once()
    if (!storeData) throw new Error('Store not initialized — restart the service.')
    const resp = await adminCall(
      LICENSING_URL,
      storeData.admin_api_key,
      '/v1/admin/zaprite/status',
      { method: 'GET' },
    )
    if (!resp.ok) {
      throw new Error(
        `Could not read status: HTTP ${resp.status} — ${await resp.text()}`,
      )
    }
    const body = (await resp.json()) as {
      connected: boolean
      webhook_url: string
      webhook_explainer: string
    }
    if (!body.connected) {
      return {
        version: '1',
        title: 'Zaprite not connected',
        message:
          'Connect Zaprite first via the Connect Zaprite action. ' +
          'Once connected, re-run this action to see the webhook URL ' +
          'to register in your Zaprite dashboard.',
        result: null,
      }
    }
    return {
      version: '1',
      title: 'Zaprite webhook setup',
      message:
        `Paste this URL exactly into Zaprite's webhook form ` +
        `(app.zaprite.com → Settings → Webhooks → New webhook):\n\n` +
        `   ${body.webhook_url}\n\n` +
        `Subscribe to order events (payment_received, settled, refunded).\n\n` +
        body.webhook_explainer,
      result: null,
    }
  },
)

/** Counterpart to Connect — clears stored credentials + active provider. */
export const disconnectZaprite = sdk.Action.withoutInput(
  'disconnect-zaprite',
  async () => ({
    name: 'Disconnect Zaprite',
    description:
      'Disconnect Keysat from your Zaprite account. Wipes the stored ' +
      'API key and clears the active provider. Existing license keys ' +
      'are unaffected. Run this before re-running Connect Zaprite if ' +
      'you want to rotate the key or switch organizations.',
    warning:
      "Don't forget to also delete the corresponding webhook in your " +
      "Zaprite dashboard — Keysat can't programmatically delete it for " +
      'you because the webhook-management API surface is not on the ' +
      'public Zaprite OpenAPI we have access to.',
    allowedStatuses: 'only-running',
    group: 'Zaprite',
    visibility: 'enabled',
  }),
  async ({ effects: _effects }) => {
    const storeData = await store.read().once()
    if (!storeData) throw new Error('Store not initialized — restart the service.')
    const resp = await adminCall(
      LICENSING_URL,
      storeData.admin_api_key,
      '/v1/admin/zaprite/disconnect',
      { method: 'POST' },
    )
    if (!resp.ok) {
      throw new Error(`Disconnect failed: HTTP ${resp.status} — ${await resp.text()}`)
    }
    const body = (await resp.json()) as
      | { ok: true; noop: true; message: string }
      | { ok: true; noop: false; message: string }
    return {
      version: '1',
      title: body.noop ? 'Already disconnected' : 'Zaprite disconnected',
      message: body.message,
      result: null,
    }
  },
)

/** Quick read-only check of the current connection state. */
export const zapriteStatus = sdk.Action.withoutInput(
  'zaprite-status',
  async () => ({
    name: 'Check Zaprite connection',
    description: 'Show whether Zaprite is the active payment provider and the configured base URL.',
    warning: null,
    allowedStatuses: 'only-running',
    group: 'Zaprite',
    visibility: 'enabled',
  }),
  async ({ effects: _effects }) => {
    const storeData = await store.read().once()
    if (!storeData) throw new Error('Store not initialized — restart the service.')
    const resp = await adminCall(
      LICENSING_URL,
      storeData.admin_api_key,
      '/v1/admin/zaprite/status',
      { method: 'GET' },
    )
    if (!resp.ok) {
      throw new Error(`Status check failed: HTTP ${resp.status} — ${await resp.text()}`)
    }
    const body = (await resp.json()) as {
      connected: boolean
      active_provider: string | null
      base_url: string | null
      webhook_id: string | null
    }
    if (!body.connected) {
      return {
        version: '1',
        title: 'Zaprite not connected',
        message: 'No Zaprite credentials configured. Run "Connect Zaprite" to paste in an API key.',
        result: null,
      }
    }
    return {
      version: '1',
      title: body.active_provider === 'zaprite' ? 'Zaprite is active' : 'Zaprite configured (provider not active)',
      message:
        `Connected to ${body.base_url ?? '(unknown URL)'}.\n` +
        `Active provider: ${body.active_provider ?? '(none)'}.` +
        (body.active_provider === 'zaprite'
          ? ''
          : "\n\nA different provider (likely BTCPay) is currently active. Disconnect that one first if you want Zaprite to take over."),
      result: null,
    }
  },
)
