'use strict';
/**
 * Durable device identity for the BrightSign shell.
 *
 * The identity JSON holds the deviceToken — "the only secret on the device" (src/provisioning/states.ts).
 * It is written with the reliability-Tier-3 discipline the audit asked for on removable SD media:
 *   - atomic write: tmp + (best-effort) fsync + rename, so a power cut mid-write never truncates it;
 *   - a `.bak` copy kept, and load() recovers from `.bak` instead of wiping on corruption;
 *   - a duid mirror in the BrightSign registry (survives an SD reformat of the data partition);
 *   - setIdentity enforces the SAME anti-hijack rules as the desktop bridge: ≤64KB, must contain a string
 *     deviceToken, and MUST NOT silently repoint a paired device to a different duid (re-pairing must clear).
 *
 * NOTE (audit R2-BS-01): the token is stored in cleartext, same as Android/desktop today. BrightSignOS can
 * encrypt the SD storage (key in the private registry); enabling that is a deployment step documented in the
 * README — this module keeps the plaintext-compatible shape and is ready to be wrapped by SD encryption.
 */
const fs = require('fs');
const path = require('path');
const device = require('./device');

const REG_SECTION = 'videomood';
const REG_DUID_KEY = 'duid';

function createIdentity(dataDir) {
	const FILE = path.join(dataDir, 'identity.json');
	const BAK = path.join(dataDir, 'identity.json.bak');
	const HWID_FILE = path.join(dataDir, 'hardware-id');

	function ensureDir() {
		try {
			fs.mkdirSync(dataDir, { recursive: true });
		} catch (e) {
			/* ignore */
		}
	}

	function parse(raw) {
		try {
			const o = JSON.parse(raw);
			if (o && typeof o === 'object' && typeof o.deviceToken === 'string' && o.deviceToken.length) return o;
		} catch (e) {
			/* fallthrough */
		}
		return null;
	}

	/** Read the identity JSON string, recovering from `.bak` if the primary file is corrupt. Never throws. */
	function readRaw() {
		try {
			const raw = fs.readFileSync(FILE, 'utf8');
			if (parse(raw)) return raw;
		} catch (e) {
			/* try bak */
		}
		try {
			const raw = fs.readFileSync(BAK, 'utf8');
			if (parse(raw)) {
				// primary was bad → restore from the good backup
				atomicWrite(FILE, raw);
				return raw;
			}
		} catch (e) {
			/* none */
		}
		return null;
	}

	function readObj() {
		return parse(readRaw() || '');
	}

	function atomicWrite(file, data) {
		ensureDir();
		const tmp = file + '.tmp';
		let fd;
		try {
			fd = fs.openSync(tmp, 'w');
			fs.writeSync(fd, data);
			try {
				fs.fsyncSync(fd);
			} catch (e) {
				/* fsync unsupported on some FS — best effort */
			}
		} finally {
			if (fd != null) fs.closeSync(fd);
		}
		fs.renameSync(tmp, file);
	}

	/** Bridge getIdentity(): the persisted identity JSON, or null. */
	function getIdentity() {
		return readRaw();
	}

	/**
	 * Bridge setIdentity(json): persist after pairing / token refresh. Enforces:
	 *  - string ≤ 64KB with a string deviceToken;
	 *  - duid-immutability: reject a write that changes the duid of an already-paired device (anti-repoint).
	 * Returns true if written.
	 */
	function setIdentity(json) {
		if (typeof json !== 'string' || json.length > 64 * 1024) return false;
		const next = parse(json);
		if (!next) return false;
		const cur = readObj();
		if (cur && cur.duid && next.duid && cur.duid !== next.duid) {
			console.log('[identity] rejected: duid change on a paired device (re-pairing must clearIdentity first)');
			return false;
		}
		try {
			// write the .bak first (old good copy preserved), then the primary, then mirror the duid in registry
			try {
				const prev = readRaw();
				if (prev) atomicWrite(BAK, prev);
			} catch (e) {
				/* ignore */
			}
			atomicWrite(FILE, json);
			if (next.duid) device.registryWrite(REG_SECTION, REG_DUID_KEY, next.duid);
			return true;
		} catch (e) {
			console.log('[identity] write failed:', e && e.message);
			return false;
		}
	}

	/** Bridge clearIdentity(): unbind / fs-wipeout / auth-failure. Removes both copies + the registry mirror. */
	function clearIdentity() {
		for (const f of [FILE, BAK]) {
			try {
				fs.unlinkSync(f);
			} catch (e) {
				/* ignore */
			}
		}
		device.registryDelete(REG_SECTION, REG_DUID_KEY);
	}

	function deviceToken() {
		const o = readObj();
		return o ? o.deviceToken || null : null;
	}

	/**
	 * Stable hardware id for plane recovery: the unit serial number when on-device, else a persisted random
	 * UUID. The plane matches hardwareId → recovers the SAME duid after storage loss (deployment.md).
	 */
	function getHardwareId() {
		const serial = device.serialNumber();
		if (serial) return serial;
		try {
			return fs.readFileSync(HWID_FILE, 'utf8').trim() || mkHwid();
		} catch (e) {
			return mkHwid();
		}
	}
	function mkHwid() {
		const id = require('crypto').randomBytes(16).toString('hex');
		try {
			ensureDir();
			fs.writeFileSync(HWID_FILE, id);
		} catch (e) {
			/* ignore */
		}
		return id;
	}

	return { getIdentity, setIdentity, clearIdentity, deviceToken, getHardwareId, readObj };
}

module.exports = { createIdentity };
