/**
 * Slice 5 ST14 / AC10 / ID-06 — `apt-tools roadmap convex-mirror`.
 *
 * Helper that POSTs the rendered narrator prose to the Next.js
 * /api/roadmap/narrator-mirror endpoint. Markdown is canonical
 * (ADR-0006); this call is the Convex mirror.
 *
 * Fail-soft contract:
 *   - 200 {mirrored:true}      → exit 0, status='ok'
 *   - 200 {mirrored:false,queued:true} → exit 0, status='queued'
 *   - 401                       → exit 1, status='error'  (auth misconfig)
 *   - 4xx (other than 401)      → exit 1, status='error'
 *   - 5xx / network error       → exit 0, status='queued', appends a
 *                                  pending JSONL row to
 *                                  .aperant/digests/narrator-mirror-pending.jsonl
 *                                  so a follow-up narrator invocation
 *                                  can replay locally if the cron
 *                                  drainer hasn't caught up.
 *
 * Token resolution order (per ID-07):
 *   1. process.env.MCP_NARRATOR_TOKEN
 *   2. .aperant/team.json :: narratorMirrorToken
 */

import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs'
import { dirname, join } from 'node:path'

function resolveToken(projectDir) {
	const envToken = process.env.MCP_NARRATOR_TOKEN
	if (envToken && envToken.length > 0) return envToken
	const teamJsonPath = join(projectDir, '.aperant', 'team.json')
	if (!existsSync(teamJsonPath)) return null
	try {
		const parsed = JSON.parse(readFileSync(teamJsonPath, 'utf-8'))
		if (typeof parsed.narratorMirrorToken === 'string' && parsed.narratorMirrorToken.length > 0) {
			return parsed.narratorMirrorToken
		}
	} catch {
		// Bad JSON — fall through to null
	}
	return null
}

function appendPendingJsonl(projectDir, row) {
	const path = join(projectDir, '.aperant', 'digests', 'narrator-mirror-pending.jsonl')
	mkdirSync(dirname(path), { recursive: true })
	appendFileSync(path, `${JSON.stringify(row)}\n`)
}

/**
 * Run the convex-mirror call.
 *
 * @param {string} projectDir
 * @param {{scope: string, phaseId: string, anchorId: string, proseBody: string, mergedPr?: number, baseUrl?: string, fetchImpl?: typeof fetch}} args
 * @returns {Promise<{status: 'ok'|'queued'|'error', code?: number, message?: string}>}
 */
export async function runConvexMirror(projectDir, args) {
	const { scope, phaseId, anchorId, proseBody, mergedPr, baseUrl, fetchImpl } = args
	if (!scope || !phaseId || !anchorId || typeof proseBody !== 'string') {
		return { status: 'error', message: 'missing required: scope, phaseId, anchorId, proseBody' }
	}
	const token = resolveToken(projectDir)
	if (!token) {
		return {
			status: 'error',
			message:
				'No narrator token (set MCP_NARRATOR_TOKEN or .aperant/team.json::narratorMirrorToken)',
		}
	}
	const url = `${(baseUrl || process.env.APERANT_BASE_URL || 'https://app.aperant.com').replace(/\/$/, '')}/api/roadmap/narrator-mirror`
	const body = {
		scope,
		phaseId,
		narratorAnchorId: anchorId,
		narratorProse: proseBody,
		mergedPr,
	}
	const f = fetchImpl ?? fetch
	let res
	try {
		res = await f(url, {
			method: 'POST',
			headers: {
				Authorization: `Bearer ${token}`,
				'Content-Type': 'application/json',
			},
			body: JSON.stringify(body),
		})
	} catch (e) {
		// Network failure → fail-soft queue.
		const msg = e instanceof Error ? e.message : String(e)
		appendPendingJsonl(projectDir, {
			...body,
			lastError: `network: ${msg}`,
			ts: new Date().toISOString(),
		})
		return { status: 'queued', message: `network error: ${msg}` }
	}
	if (res.status === 401) {
		return { status: 'error', code: 401, message: 'auth: invalid or missing token' }
	}
	if (res.status >= 500) {
		// 5xx → fail-soft queue.
		appendPendingJsonl(projectDir, {
			...body,
			lastError: `http ${res.status}`,
			ts: new Date().toISOString(),
		})
		return { status: 'queued', code: res.status, message: 'server error — enqueued' }
	}
	if (res.status >= 400) {
		let detail = ''
		try {
			detail = await res.text()
		} catch {
			// ignore
		}
		return { status: 'error', code: res.status, message: detail || `http ${res.status}` }
	}
	// 200/2xx
	let parsed
	try {
		parsed = await res.json()
	} catch {
		parsed = { mirrored: true, queued: false }
	}
	if (parsed?.queued) {
		return { status: 'queued', code: res.status, message: 'enqueued server-side' }
	}
	return { status: 'ok', code: res.status }
}
