/**
 * commands/driver-doctor.mjs — `apt-tools driver doctor <projectDir>`.
 *
 * Validates every discovered driver against:
 *   1. The manifest JSON-Schema (manifest-validator.mjs).
 *   2. IDL conformance — every declared capability has a function export.
 *      NOTE: this step necessarily imports driver.mjs, which RUNS the
 *      driver's top-level module code. For that reason we trust-gate the
 *      import: bundled drivers (first-party signed, sitting under
 *      packages/framework/drivers/) are always imported; user drivers
 *      under .aperant/drivers/ are imported only when policy + TOFU say
 *      it is safe. A user driver that fails the gate is reported with
 *      `idl_conformant: false` and `idl_skipped_reason: <decision>`
 *      rather than executed.
 *   3. Preflight commands (declared as structured local_command per
 *      ID-03). For each preflight whose declared transport is the
 *      structured `{ binary, args[] }` shape, we run it via execFile
 *      (exec.mjs — no shell) and record pass/fail + missing_deps.
 *      Preflights without a structured `command` (declaration-only) are
 *      marked `pending` — there is nothing to execute.
 *
 * Emits a JSON envelope listing pass/fail per driver. Third-party driver
 * authors invoke this before publishing.
 */

import { existsSync, readdirSync, readFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { err, ok } from '../util/result.mjs'
import { execLocalCommand } from '../verify-proof/exec.mjs'
import { IDL_VERB_NAMES } from '../verify-proof/idl/index.mjs'
import { validateManifest } from '../verify-proof/manifest-validator.mjs'
import { decide, readPolicy, readTofuLock } from '../verify-proof/trust.mjs'

/**
 * @typedef {Object} DriverDoctorReport
 * @property {string} driverId
 * @property {string} version
 * @property {string} locality
 * @property {boolean} manifest_valid
 * @property {Array<{path: string, message: string}>} manifest_errors
 * @property {boolean} idl_conformant
 * @property {string[]} missing_exports
 * @property {string[]} unknown_capabilities
 * @property {string} [idl_skipped_reason] — present when conformance was skipped because the trust gate blocked driver.mjs import
 * @property {'pending' | 'pass' | 'fail' | 'skipped'} preflight_status
 * @property {string[]} missing_deps
 * @property {string} [publisher_warning] — surfaced when a non-first-party manifest claims first_party_signed (v1 has no signature verification)
 */

/**
 * Scan a drivers directory for manifest.json files. Returns
 * (manifest, dir, locality) tuples.
 * @param {string} rootDir
 * @param {'bundled' | 'user'} locality
 */
function scanDriversDir(rootDir, locality) {
	const out = []
	if (!existsSync(rootDir)) return out
	for (const entry of readdirSync(rootDir, { withFileTypes: true })) {
		if (!entry.isDirectory()) continue
		const driverDir = join(rootDir, entry.name)
		const manifestPath = join(driverDir, 'manifest.json')
		if (!existsSync(manifestPath)) continue
		let manifest
		try {
			manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))
		} catch (e) {
			out.push({ malformed: true, dir: driverDir, locality, parse_error: String(e) })
			continue
		}
		out.push({ manifest, dir: driverDir, locality })
	}
	return out
}

/**
 * @param {object} manifest
 * @param {string} driverDir
 * @returns {Promise<{idl_conformant: boolean, missing_exports: string[], unknown_capabilities: string[]}>}
 */
async function checkIdlConformance(manifest, driverDir) {
	const driverPath = join(driverDir, 'driver.mjs')
	if (!existsSync(driverPath)) {
		return {
			idl_conformant: false,
			missing_exports: ['driver.mjs not found'],
			unknown_capabilities: [],
		}
	}
	const missing_exports = []
	const unknown_capabilities = []
	const idlSet = new Set(IDL_VERB_NAMES)
	try {
		const mod = await import(driverPath)
		for (const cap of manifest.capabilities ?? []) {
			if (!idlSet.has(cap)) {
				unknown_capabilities.push(cap)
				continue
			}
			if (typeof mod[cap] !== 'function') {
				missing_exports.push(cap)
			}
		}
	} catch (e) {
		return {
			idl_conformant: false,
			missing_exports: [`driver.mjs import failed: ${String(e)}`],
			unknown_capabilities: [],
		}
	}
	return {
		idl_conformant: missing_exports.length === 0 && unknown_capabilities.length === 0,
		missing_exports,
		unknown_capabilities,
	}
}

/**
 * Run a driver's preflight checks. Each declared preflight with a
 * structured `command: { binary, args[] }` is executed via execFile
 * (no shell). Preflights without a structured command are reported as
 * `pending` — there is nothing to execute. Empty preflight list is
 * `skipped`.
 *
 * @param {object} manifest
 * @returns {Promise<{preflight_status: 'pending'|'pass'|'fail'|'skipped', missing_deps: string[]}>}
 */
async function runPreflight(manifest) {
	const preflight = Array.isArray(manifest.preflight) ? manifest.preflight : []
	if (preflight.length === 0) return { preflight_status: 'skipped', missing_deps: [] }

	const missing_deps = []
	let executed = 0
	let failed = false

	for (const check of preflight) {
		// Only execute when the preflight declares a structured local-command
		// shape. Anything else (no command, or non-structured) is left as
		// declaration-only — caller can drive it explicitly.
		const cmd = check?.command
		if (
			cmd === undefined ||
			cmd === null ||
			typeof cmd !== 'object' ||
			typeof cmd.binary !== 'string' ||
			!Array.isArray(cmd.args) ||
			cmd.args.some((a) => typeof a !== 'string')
		) {
			continue
		}
		executed++
		try {
			await execLocalCommand({ transport: { local_command: cmd } }, [], { timeout: 5000 })
		} catch {
			failed = true
			if (check.required !== false) {
				missing_deps.push(check.name ?? cmd.binary)
			}
		}
	}

	if (executed === 0) return { preflight_status: 'pending', missing_deps: [] }
	return { preflight_status: failed ? 'fail' : 'pass', missing_deps }
}

/**
 * Trust-gate the driver.mjs import. Bundled drivers (locality='bundled')
 * always pass — they live in packages/framework/drivers/ and are first-
 * party. User drivers go through the policy + TOFU decision; only
 * `allow` permits the dynamic import.
 *
 * @param {object} manifest
 * @param {'bundled'|'user'} locality
 * @param {string} projectDir
 * @returns {{ok: true} | {ok: false, reason: string}}
 */
function trustGate(manifest, locality, projectDir) {
	if (locality === 'bundled') return { ok: true }
	const policy = readPolicy(projectDir)
	const tofu = readTofuLock(projectDir)
	const decision = decide(manifest, tofu, policy)
	if (decision.decision === 'allow') return { ok: true }
	return { ok: false, reason: decision.reason }
}

/**
 * v1 has no signature verification (spec §Out of Scope). A non-Aperant
 * publisher claiming `first_party_signed` is suspicious — surface a
 * warning so the operator notices.
 *
 * @param {object} manifest
 * @returns {string | undefined}
 */
function publisherWarning(manifest) {
	const tier = manifest?.trust?.tier
	if (tier !== 'first_party_signed') return undefined
	const npmScope = manifest?.trust?.publisher?.npm_scope
	const githubOrg = manifest?.trust?.publisher?.github_org
	const looksLikeAperant = npmScope === '@aperant' || githubOrg === 'Mikalsen-AI'
	if (looksLikeAperant) return undefined
	return `trust.tier=first_party_signed but publisher is not @aperant — v1 does not verify signatures; treat tier as advisory`
}

/**
 * @param {string} projectDir
 * @param {string[]} [_extra]
 */
export async function cmdDriverDoctor(projectDir, _extra = []) {
	if (!projectDir) return err('Usage: apt-tools driver doctor <project-dir>')
	const target = resolve(projectDir)

	// fileURLToPath is portable across POSIX + Windows; new URL(...).pathname
	// returns '/C:/...' on Windows which resolve() does not handle correctly.
	const frameworkRoot = fileURLToPath(new URL('../../..', import.meta.url))
	const bundled = scanDriversDir(join(frameworkRoot, 'drivers'), 'bundled')
	const user = scanDriversDir(join(target, '.aperant/drivers'), 'user')
	const discovered = [...bundled, ...user]

	const drivers = []
	for (const d of discovered) {
		if (d.malformed) {
			drivers.push({
				driverId: '(malformed)',
				version: '?',
				locality: d.locality,
				manifest_valid: false,
				manifest_errors: [{ path: '/', message: d.parse_error }],
				idl_conformant: false,
				missing_exports: [],
				unknown_capabilities: [],
				preflight_status: 'skipped',
				missing_deps: [],
			})
			continue
		}

		const manifest = d.manifest
		const schemaResult = validateManifest(manifest)
		const manifest_valid = schemaResult.valid === true
		const manifest_errors = schemaResult.valid === false ? schemaResult.errors : []

		let idlResult = { idl_conformant: false, missing_exports: [], unknown_capabilities: [] }
		let idl_skipped_reason
		if (manifest_valid) {
			const gate = trustGate(manifest, d.locality, target)
			if (gate.ok) {
				idlResult = await checkIdlConformance(manifest, d.dir)
			} else {
				// Trust gate denied. Do NOT dynamic-import the driver — that
				// would execute arbitrary third-party code under the audit
				// command. Report idl_conformant=false with the gate reason
				// so the operator sees exactly why conformance was not run.
				idl_skipped_reason = `trust gate denied: ${gate.reason}`
			}
		}

		let preflightResult = { preflight_status: 'skipped', missing_deps: [] }
		if (manifest_valid) {
			preflightResult = await runPreflight(manifest)
		}

		const entry = {
			driverId: manifest.driverId ?? '(unknown)',
			version: manifest.version ?? '?',
			locality: d.locality,
			manifest_valid,
			manifest_errors,
			idl_conformant: idlResult.idl_conformant,
			missing_exports: idlResult.missing_exports,
			unknown_capabilities: idlResult.unknown_capabilities,
			preflight_status: preflightResult.preflight_status,
			missing_deps: preflightResult.missing_deps,
		}
		if (idl_skipped_reason !== undefined) entry.idl_skipped_reason = idl_skipped_reason
		const warning = publisherWarning(manifest)
		if (warning !== undefined) entry.publisher_warning = warning
		drivers.push(entry)
	}

	const all_pass = drivers.every((d) => d.manifest_valid && d.idl_conformant)
	return ok({
		status: 'ok',
		command: 'driver-doctor',
		projectDir: target,
		drivers,
		summary: {
			total: drivers.length,
			pass: drivers.filter((d) => d.manifest_valid && d.idl_conformant).length,
			fail: drivers.filter((d) => !d.manifest_valid || !d.idl_conformant).length,
			overall: all_pass ? 'pass' : 'fail',
		},
	})
}
