/**
 * CRDT Token Service — server-side token management via Automerge.
 *
 * Manages token ledger documents: minting, balance queries, and seeding.
 */

import * as Automerge from '@automerge/automerge';
import type { SyncServer } from './local-first/sync-server';
import { tokenLedgerSchema, tokenDocId } from './token-schemas';
import type { TokenLedgerDoc, LedgerEntry } from './token-schemas';

let _syncServer: SyncServer | null = null;

export function initTokenService(syncServer: SyncServer) {
	_syncServer = syncServer;
}

/** Create a token doc if it doesn't exist yet. */
function ensureTokenDoc(tokenId: string): TokenLedgerDoc {
	const docId = tokenDocId(tokenId);
	let doc = _syncServer!.getDoc<TokenLedgerDoc>(docId);
	if (!doc) {
		doc = Automerge.change(Automerge.init<TokenLedgerDoc>(), 'init token ledger', (d) => {
			const init = tokenLedgerSchema.init();
			Object.assign(d, init);
		});
		_syncServer!.setDoc(docId, doc);
	}
	return doc;
}

/** Sum all entry amounts for a given DID holder. Excludes reversed burns. */
export function getBalance(doc: TokenLedgerDoc, did: string): number {
	let balance = 0;
	for (const entry of Object.values(doc.entries)) {
		// Skip reversed burns (the compensating mint handles the refund)
		if (entry.type === 'burn' && (entry as any).status === 'reversed') continue;
		if (entry.type === 'mint' && entry.to === did) {
			balance += entry.amount;
		} else if (entry.type === 'transfer') {
			if (entry.to === did) balance += entry.amount;
			if (entry.from === did) balance -= entry.amount;
		} else if (entry.type === 'burn' && entry.from === did) {
			balance -= entry.amount;
		}
	}
	return balance;
}

/** Aggregate balances for all holders. */
export function getAllBalances(doc: TokenLedgerDoc): Record<string, { did: string; label: string; balance: number }> {
	const holders: Record<string, { did: string; label: string; balance: number }> = {};

	for (const entry of Object.values(doc.entries)) {
		if (entry.type === 'mint') {
			if (!holders[entry.to]) holders[entry.to] = { did: entry.to, label: entry.toLabel, balance: 0 };
			holders[entry.to].balance += entry.amount;
			if (entry.toLabel) holders[entry.to].label = entry.toLabel;
		} else if (entry.type === 'transfer') {
			if (!holders[entry.to]) holders[entry.to] = { did: entry.to, label: entry.toLabel, balance: 0 };
			if (!holders[entry.from]) holders[entry.from] = { did: entry.from, label: '', balance: 0 };
			holders[entry.to].balance += entry.amount;
			holders[entry.from].balance -= entry.amount;
		} else if (entry.type === 'burn') {
			if (!holders[entry.from]) holders[entry.from] = { did: entry.from, label: '', balance: 0 };
			holders[entry.from].balance -= entry.amount;
		}
	}

	// Remove zero-balance holders
	for (const [k, v] of Object.entries(holders)) {
		if (v.balance <= 0) delete holders[k];
	}
	return holders;
}

/** Transfer tokens between DIDs. */
export function transferTokens(
	tokenId: string,
	fromDid: string,
	fromLabel: string,
	toDid: string,
	toLabel: string,
	amount: number,
	memo: string,
	issuedBy: string,
	timestamp?: number,
): boolean {
	const docId = tokenDocId(tokenId);
	ensureTokenDoc(tokenId);

	const entryId = `xfer-${timestamp || Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
	const result = _syncServer!.changeDoc<TokenLedgerDoc>(docId, `transfer ${amount} from ${fromLabel} to ${toLabel}`, (d) => {
		d.entries[entryId] = {
			id: entryId,
			to: toDid,
			toLabel,
			amount,
			memo,
			type: 'transfer',
			from: fromDid,
			timestamp: timestamp || Date.now(),
			issuedBy,
		};
	});
	return result !== null;
}

/** Get all ledger entries across all tokens, optionally filtered by DID. */
export function getAllTransfers(filterDid?: string): Array<LedgerEntry & { tokenId: string; tokenSymbol: string; tokenDecimals: number; tokenIcon: string }> {
	const docIds = listTokenDocs();
	const entries: Array<LedgerEntry & { tokenId: string; tokenSymbol: string; tokenDecimals: number; tokenIcon: string }> = [];

	for (const docId of docIds) {
		const tokenId = docId.replace('global:tokens:ledgers:', '');
		const doc = getTokenDoc(tokenId);
		if (!doc || !doc.token.name) continue;

		for (const entry of Object.values(doc.entries)) {
			if (filterDid && entry.to !== filterDid && entry.from !== filterDid) continue;
			entries.push({
				...entry,
				tokenId: doc.token.id,
				tokenSymbol: doc.token.symbol,
				tokenDecimals: doc.token.decimals,
				tokenIcon: doc.token.icon,
			});
		}
	}

	return entries.sort((a, b) => a.timestamp - b.timestamp);
}

/** Mint tokens to a DID. */
export function mintTokens(
	tokenId: string,
	toDid: string,
	toLabel: string,
	amount: number,
	memo: string,
	issuedBy: string,
	timestamp?: number,
	extra?: { txHash?: string; onChainNetwork?: string },
): boolean {
	const docId = tokenDocId(tokenId);
	ensureTokenDoc(tokenId);

	const entryId = `mint-${timestamp || Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
	const result = _syncServer!.changeDoc<TokenLedgerDoc>(docId, `mint ${amount} to ${toLabel}`, (d) => {
		d.entries[entryId] = {
			id: entryId,
			to: toDid,
			toLabel,
			amount,
			memo,
			type: 'mint',
			from: '',
			timestamp: timestamp || Date.now(),
			issuedBy,
		};
		if (extra?.txHash) d.entries[entryId].txHash = extra.txHash;
		if (extra?.onChainNetwork) d.entries[entryId].onChainNetwork = extra.onChainNetwork;
		d.token.totalSupply = (d.token.totalSupply || 0) + amount;
	});
	return result !== null;
}

/**
 * Mint cUSDC from an on-chain USDC payment (Direction 1: x402 → CRDT).
 * Idempotent — skips if an entry with the same txHash already exists.
 */
export function mintFromOnChain(
	did: string,
	label: string,
	amountDecimal: string,
	txHash: string,
	network: string,
): boolean {
	const tokenId = 'cusdc';
	const doc = ensureTokenDoc(tokenId);

	// Idempotency: check if txHash already minted
	for (const entry of Object.values(doc.entries)) {
		if (entry.txHash === txHash) {
			console.log(`[TokenService] x402 bridge: txHash ${txHash} already minted, skipping`);
			return false;
		}
	}

	// Convert decimal USDC string to cUSDC base units (6 decimals)
	const amount = Math.round(parseFloat(amountDecimal) * 1_000_000);
	if (amount <= 0 || isNaN(amount)) {
		console.warn(`[TokenService] x402 bridge: invalid amount "${amountDecimal}"`);
		return false;
	}

	const success = mintTokens(
		tokenId, did, label, amount,
		`x402 bridge: on-chain USDC → cUSDC (tx: ${txHash.slice(0, 10)}...)`,
		'x402-bridge',
		undefined,
		{ txHash, onChainNetwork: network },
	);

	if (success) {
		console.log(`[TokenService] x402 bridge: minted ${amount} cUSDC to ${label} (${did}) from tx ${txHash.slice(0, 10)}...`);
	}
	return success;
}

/** Burn tokens from a DID. */
export function burnTokens(
	tokenId: string,
	fromDid: string,
	fromLabel: string,
	amount: number,
	memo: string,
	issuedBy: string,
	timestamp?: number,
): boolean {
	const docId = tokenDocId(tokenId);
	ensureTokenDoc(tokenId);

	const entryId = `burn-${timestamp || Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
	const result = _syncServer!.changeDoc<TokenLedgerDoc>(docId, `burn ${amount} from ${fromLabel}`, (d) => {
		d.entries[entryId] = {
			id: entryId,
			to: '',
			toLabel: '',
			amount,
			memo,
			type: 'burn',
			from: fromDid,
			timestamp: timestamp || Date.now(),
			issuedBy,
		};
		d.token.totalSupply = Math.max(0, (d.token.totalSupply || 0) - amount);
	});
	return result !== null;
}

/**
 * Burn tokens into escrow (Phase 4 off-ramp).
 * Creates a burn entry with offRampId + status:'escrow'. Balance reduced immediately.
 */
export function burnTokensEscrow(
	tokenId: string,
	fromDid: string,
	fromLabel: string,
	amount: number,
	offRampId: string,
	memo: string,
): boolean {
	const docId = tokenDocId(tokenId);
	ensureTokenDoc(tokenId);

	const entryId = `escrow-${offRampId}`;
	const result = _syncServer!.changeDoc<TokenLedgerDoc>(docId, `escrow burn ${amount} from ${fromLabel}`, (d) => {
		d.entries[entryId] = {
			id: entryId,
			to: '',
			toLabel: '',
			amount,
			memo,
			type: 'burn',
			from: fromDid,
			timestamp: Date.now(),
			issuedBy: 'offramp-service',
			offRampId,
			status: 'escrow',
		} as any; // extended fields
		d.token.totalSupply = Math.max(0, (d.token.totalSupply || 0) - amount);
	});
	return result !== null;
}

/** Confirm an escrow burn (payout succeeded). */
export function confirmBurn(tokenId: string, offRampId: string): boolean {
	const docId = tokenDocId(tokenId);
	const doc = ensureTokenDoc(tokenId);
	const entryId = `escrow-${offRampId}`;
	if (!doc.entries[entryId]) return false;

	const result = _syncServer!.changeDoc<TokenLedgerDoc>(docId, `confirm escrow ${offRampId}`, (d) => {
		if (d.entries[entryId]) {
			(d.entries[entryId] as any).status = 'confirmed';
		}
	});
	return result !== null;
}

/** Reverse an escrow burn (payout failed) — mints tokens back to the original holder. */
export function reverseBurn(tokenId: string, offRampId: string): boolean {
	const docId = tokenDocId(tokenId);
	const doc = ensureTokenDoc(tokenId);
	const entryId = `escrow-${offRampId}`;
	const entry = doc.entries[entryId];
	if (!entry) return false;

	const result = _syncServer!.changeDoc<TokenLedgerDoc>(docId, `reverse escrow ${offRampId}`, (d) => {
		// Mark original burn as reversed
		if (d.entries[entryId]) {
			(d.entries[entryId] as any).status = 'reversed';
		}
		// Compensating mint to refund the holder
		const refundId = `refund-${offRampId}`;
		d.entries[refundId] = {
			id: refundId,
			to: entry.from,
			toLabel: '',
			amount: entry.amount,
			memo: `Refund: off-ramp ${offRampId} reversed`,
			type: 'mint',
			from: '',
			timestamp: Date.now(),
			issuedBy: 'offramp-service',
		};
		d.token.totalSupply = (d.token.totalSupply || 0) + entry.amount;
	});
	return result !== null;
}

/** List all token doc IDs. */
export function listTokenDocs(): string[] {
	return _syncServer!.listDocs().filter((id) => id.startsWith('global:tokens:ledgers:'));
}

/** Get a token doc by ID. */
export function getTokenDoc(tokenId: string): TokenLedgerDoc | undefined {
	return _syncServer!.getDoc<TokenLedgerDoc>(tokenDocId(tokenId));
}

/** Seed the full DAO token ecosystem — BFT governance, cUSDC stablecoin, multiple treasuries. */
export async function seedCUSDC() {
	// Check if already seeded by looking at $MYCO token (last one created)
	const mycoDoc = getTokenDoc('myco');
	if (mycoDoc && mycoDoc.token.name && Object.keys(mycoDoc.entries).length > 0) {
		console.log('[TokenService] DAO ecosystem already seeded, skipping');
		return;
	}

	// ── DAO participants ──
	const jeffDid = process.env.SEED_JEFF_DID || 'did:key:jAV6y4tg8UbKJEkN0npvX8CTdJkSCGpU';
	const treasuryDid   = 'did:key:treasury-main-rspace-dao-2026';
	const grantsDid     = 'did:key:treasury-grants-committee-2026';
	const devFundDid    = 'did:key:treasury-dev-fund-rspace-2026';
	const aliceDid      = 'did:key:alice-contributor-rspace-2026';
	const bobDid        = 'did:key:bob-auditor-rspace-2026';
	const carolDid      = 'did:key:carol-designer-rspace-2026';

	// Timestamps spread over the last 60 days
	const now = Date.now();
	const day = 86400000;
	const t = (daysAgo: number) => now - daysAgo * day;

	// ── 1. cUSDC — CRDT stablecoin ──
	ensureTokenDef('cusdc', {
		name: 'CRDT USDC', symbol: 'cUSDC', decimals: 6,
		description: 'CRDT-native stablecoin pegged to USDC, stored as an Automerge document',
		icon: '💵', color: '#2775ca',
	});

	// Mint to main treasury
	mintTokens('cusdc', treasuryDid, 'DAO Treasury', 500_000_000_000, 'Genesis allocation — 500K cUSDC', 'system', t(58));
	// Treasury funds grants committee
	transferTokens('cusdc', treasuryDid, 'DAO Treasury', grantsDid, 'Grants Committee', 100_000_000_000, 'Q1 2026 grants budget', 'system', t(55));
	// Treasury funds dev fund
	transferTokens('cusdc', treasuryDid, 'DAO Treasury', devFundDid, 'Dev Fund', 75_000_000_000, 'Q1 2026 development budget', 'system', t(54));
	// Grants committee pays contributors
	transferTokens('cusdc', grantsDid, 'Grants Committee', aliceDid, 'Alice', 15_000_000_000, 'Grant: local-first sync engine', 'system', t(48));
	transferTokens('cusdc', grantsDid, 'Grants Committee', bobDid, 'Bob', 8_000_000_000, 'Grant: security audit Q1', 'system', t(45));
	transferTokens('cusdc', grantsDid, 'Grants Committee', carolDid, 'Carol', 12_000_000_000, 'Grant: UX redesign sprint', 'system', t(42));
	// Dev fund pays jeff
	transferTokens('cusdc', devFundDid, 'Dev Fund', jeffDid, 'jeff', 25_000_000_000, 'Core dev compensation — Jan', 'system', t(40));
	transferTokens('cusdc', devFundDid, 'Dev Fund', aliceDid, 'Alice', 10_000_000_000, 'CRDT module development', 'system', t(35));
	// Second round
	transferTokens('cusdc', grantsDid, 'Grants Committee', bobDid, 'Bob', 5_000_000_000, 'Follow-up audit: identity module', 'system', t(30));
	transferTokens('cusdc', devFundDid, 'Dev Fund', jeffDid, 'jeff', 25_000_000_000, 'Core dev compensation — Feb', 'system', t(25));
	transferTokens('cusdc', treasuryDid, 'DAO Treasury', grantsDid, 'Grants Committee', 50_000_000_000, 'Grants top-up — mid-quarter', 'system', t(22));
	transferTokens('cusdc', grantsDid, 'Grants Committee', carolDid, 'Carol', 8_000_000_000, 'Design system components', 'system', t(18));
	// Recent activity
	transferTokens('cusdc', devFundDid, 'Dev Fund', jeffDid, 'jeff', 25_000_000_000, 'Core dev compensation — Mar', 'system', t(10));
	transferTokens('cusdc', treasuryDid, 'DAO Treasury', devFundDid, 'Dev Fund', 50_000_000_000, 'Q2 2026 dev budget (early)', 'system', t(5));
	transferTokens('cusdc', grantsDid, 'Grants Committee', aliceDid, 'Alice', 20_000_000_000, 'Grant: rMaps privacy module', 'system', t(3));

	// ── 2. $MYCO — Governance token (bonding curve) ──
	ensureTokenDef('myco', {
		name: 'MYCO Token', symbol: '$MYCO', decimals: 6,
		description: 'Governance token for the rSpace DAO — bonding curve with cUSDC reserve, used for voting and delegation',
		icon: '🌱', color: '#22c55e',
	});

	const myco = (n: number) => n * 1_000_000; // 6 effective decimals

	// Genesis mint to treasury
	mintTokens('myco', treasuryDid, 'DAO Treasury', myco(10_000_000), 'Genesis: 10M $MYCO', 'system', t(58));
	// Distribute to sub-treasuries
	transferTokens('myco', treasuryDid, 'DAO Treasury', grantsDid, 'Grants Committee', myco(2_000_000), 'Grants committee allocation', 'system', t(56));
	transferTokens('myco', treasuryDid, 'DAO Treasury', devFundDid, 'Dev Fund', myco(1_500_000), 'Dev fund allocation', 'system', t(56));
	// Vest to contributors
	transferTokens('myco', treasuryDid, 'DAO Treasury', jeffDid, 'jeff', myco(500_000), 'Founder vesting — month 1', 'system', t(50));
	transferTokens('myco', grantsDid, 'Grants Committee', aliceDid, 'Alice', myco(100_000), 'Contributor reward: sync engine', 'system', t(46));
	transferTokens('myco', grantsDid, 'Grants Committee', bobDid, 'Bob', myco(50_000), 'Contributor reward: audit', 'system', t(43));
	transferTokens('myco', grantsDid, 'Grants Committee', carolDid, 'Carol', myco(75_000), 'Contributor reward: UX', 'system', t(40));
	transferTokens('myco', devFundDid, 'Dev Fund', jeffDid, 'jeff', myco(200_000), 'Dev milestone: rwallet launch', 'system', t(35));
	// Second vesting round
	transferTokens('myco', treasuryDid, 'DAO Treasury', jeffDid, 'jeff', myco(500_000), 'Founder vesting — month 2', 'system', t(20));
	transferTokens('myco', grantsDid, 'Grants Committee', aliceDid, 'Alice', myco(150_000), 'Grant reward: rMaps', 'system', t(15));
	transferTokens('myco', devFundDid, 'Dev Fund', bobDid, 'Bob', myco(80_000), 'Security bounty payout', 'system', t(12));
	transferTokens('myco', devFundDid, 'Dev Fund', carolDid, 'Carol', myco(60_000), 'Design system completion', 'system', t(8));
	// Recent
	transferTokens('myco', treasuryDid, 'DAO Treasury', jeffDid, 'jeff', myco(500_000), 'Founder vesting — month 3', 'system', t(2));

	console.log('[TokenService] DAO ecosystem seeded: cUSDC + $MYCO tokens with treasury flows');
}

/** Helper to define a token if not yet defined. */
function ensureTokenDef(tokenId: string, def: { name: string; symbol: string; decimals: number; description: string; icon: string; color: string }) {
	const doc = ensureTokenDoc(tokenId);
	if (doc.token.name) return; // Already defined
	const docId = tokenDocId(tokenId);
	_syncServer!.changeDoc<TokenLedgerDoc>(docId, `define ${def.symbol} token`, (d) => {
		d.token.id = tokenId;
		d.token.name = def.name;
		d.token.symbol = def.symbol;
		d.token.decimals = def.decimals;
		d.token.description = def.description;
		d.token.icon = def.icon;
		d.token.color = def.color;
		d.token.createdAt = Date.now();
		d.token.createdBy = 'system';
	});
}

/** Mint $5 fUSDC welcome balance for a new user. */
export function mintWelcomeBalance(did: string, username: string): boolean {
	if (!_syncServer) {
		console.warn('[TokenService] SyncServer not initialized, skipping welcome balance');
		return false;
	}

	const tokenId = 'fusdc';
	const docId = tokenDocId(tokenId);
	let doc = _syncServer.getDoc<TokenLedgerDoc>(docId);

	// Create fUSDC token if it doesn't exist
	if (!doc) {
		doc = Automerge.change(Automerge.init<TokenLedgerDoc>(), 'init fUSDC ledger', (d) => {
			const init = tokenLedgerSchema.init();
			Object.assign(d, init);
		});
		_syncServer.setDoc(docId, doc);
	}

	if (!doc.token.name) {
		_syncServer.changeDoc<TokenLedgerDoc>(docId, 'define fUSDC token', (d) => {
			d.token.id = 'fusdc';
			d.token.name = 'Fake USDC';
			d.token.symbol = 'fUSDC';
			d.token.decimals = 6;
			d.token.description = 'Test stablecoin for new users — $5 welcome balance';
			d.token.icon = '💵';
			d.token.color = '#2775ca';
			d.token.createdAt = Date.now();
			d.token.createdBy = 'system';
		});
	}

	// Mint 5 fUSDC (5 × 10^6 base units at 6 decimals)
	const success = mintTokens(tokenId, did, username, 5_000_000, 'Welcome balance', 'system');
	if (success) {
		console.log(`[TokenService] Welcome balance: 5 fUSDC minted to ${username} (${did})`);
	}
	return success;
}
