/**
 * rExchange API routes — P2P on/off-ramp exchange.
 */

import { Hono } from 'hono';
import * as Automerge from '@automerge/automerge';
import { verifyToken, extractToken } from '../../server/auth';
import type { SyncServer } from '../../server/local-first/sync-server';
import {
	exchangeIntentsDocId, exchangeTradesDocId, exchangeReputationDocId, exchangePoolsDocId,
	exchangeIntentsSchema, exchangeTradesSchema, exchangeReputationSchema, exchangePoolsSchema,
} from './schemas';
import type {
	ExchangeIntentsDoc, ExchangeTradesDoc, ExchangeReputationDoc, ExchangePoolsDoc,
	ExchangeIntent, ExchangeTrade, ExchangeSide, TokenId, FiatCurrency, RateType,
	LiquidityPosition,
} from './schemas';
import { solveExchange } from './exchange-solver';
import { lockEscrow, releaseEscrow, reverseEscrow, resolveDispute, sweepTimeouts } from './exchange-settlement';
import { getReputation } from './exchange-reputation';
import { getExchangeRate, getAllRates } from './exchange-rates';

const VALID_TOKENS: TokenId[] = ['cusdc', 'myco', 'fusdc'];
const VALID_FIATS: FiatCurrency[] = ['EUR', 'USD', 'GBP', 'BRL', 'MXN', 'INR', 'NGN', 'ARS'];
const VALID_SIDES: ExchangeSide[] = ['buy', 'sell'];
const VALID_RATE_TYPES: RateType[] = ['fixed', 'market_plus_bps'];

export function createExchangeRoutes(getSyncServer: () => SyncServer | null) {
	const routes = new Hono();

	// ── Helpers ──

	function ss(): SyncServer {
		const s = getSyncServer();
		if (!s) throw new Error('SyncServer not initialized');
		return s;
	}

	function ensureIntentsDoc(space: string): ExchangeIntentsDoc {
		const syncServer = ss();
		const docId = exchangeIntentsDocId(space);
		let doc = syncServer.getDoc<ExchangeIntentsDoc>(docId);
		if (!doc) {
			doc = Automerge.change(Automerge.init<ExchangeIntentsDoc>(), 'init exchange intents', (d) => {
				const init = exchangeIntentsSchema.init();
				Object.assign(d, init);
				d.meta.spaceSlug = space;
			});
			syncServer.setDoc(docId, doc);
		}
		return doc;
	}

	function ensureTradesDoc(space: string): ExchangeTradesDoc {
		const syncServer = ss();
		const docId = exchangeTradesDocId(space);
		let doc = syncServer.getDoc<ExchangeTradesDoc>(docId);
		if (!doc) {
			doc = Automerge.change(Automerge.init<ExchangeTradesDoc>(), 'init exchange trades', (d) => {
				const init = exchangeTradesSchema.init();
				Object.assign(d, init);
				d.meta.spaceSlug = space;
			});
			syncServer.setDoc(docId, doc);
		}
		return doc;
	}

	function ensureReputationDoc(space: string): ExchangeReputationDoc {
		const syncServer = ss();
		const docId = exchangeReputationDocId(space);
		let doc = syncServer.getDoc<ExchangeReputationDoc>(docId);
		if (!doc) {
			doc = Automerge.change(Automerge.init<ExchangeReputationDoc>(), 'init exchange reputation', (d) => {
				const init = exchangeReputationSchema.init();
				Object.assign(d, init);
				d.meta.spaceSlug = space;
			});
			syncServer.setDoc(docId, doc);
		}
		return doc;
	}

	function ensurePoolsDoc(space: string): ExchangePoolsDoc {
		const syncServer = ss();
		const docId = exchangePoolsDocId(space);
		let doc = syncServer.getDoc<ExchangePoolsDoc>(docId);
		if (!doc) {
			doc = Automerge.change(Automerge.init<ExchangePoolsDoc>(), 'init exchange pools', (d) => {
				const init = exchangePoolsSchema.init();
				Object.assign(d, init);
				d.meta.spaceSlug = space;
			});
			syncServer.setDoc(docId, doc);
		}
		return doc;
	}

	// ── POST /api/exchange/intent — Create buy/sell intent ──

	routes.post('/api/exchange/intent', async (c) => {
		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Authentication required' }, 401);
		let claims;
		try { claims = await verifyToken(token); } catch { return c.json({ error: 'Invalid token' }, 401); }

		const space = c.req.param('space') || 'demo';
		const body = await c.req.json();

		const { side, tokenId, fiatCurrency, tokenAmountMin, tokenAmountMax, rateType } = body;

		// Validation
		if (!VALID_SIDES.includes(side)) return c.json({ error: `side must be: ${VALID_SIDES.join(', ')}` }, 400);
		if (!VALID_TOKENS.includes(tokenId)) return c.json({ error: `tokenId must be: ${VALID_TOKENS.join(', ')}` }, 400);
		if (!VALID_FIATS.includes(fiatCurrency)) return c.json({ error: `fiatCurrency must be: ${VALID_FIATS.join(', ')}` }, 400);
		if (!VALID_RATE_TYPES.includes(rateType)) return c.json({ error: `rateType must be: ${VALID_RATE_TYPES.join(', ')}` }, 400);
		if (typeof tokenAmountMin !== 'number' || typeof tokenAmountMax !== 'number' || tokenAmountMin <= 0 || tokenAmountMax < tokenAmountMin) {
			return c.json({ error: 'tokenAmountMin/Max must be positive numbers, max >= min' }, 400);
		}
		if (rateType === 'fixed' && (body.rateFixed == null || typeof body.rateFixed !== 'number')) {
			return c.json({ error: 'rateFixed required for fixed rate type' }, 400);
		}
		if (rateType === 'market_plus_bps' && (body.rateMarketBps == null || typeof body.rateMarketBps !== 'number')) {
			return c.json({ error: 'rateMarketBps required for market_plus_bps rate type' }, 400);
		}
		if (!body.paymentMethods?.length) {
			return c.json({ error: 'At least one payment method required' }, 400);
		}

		const id = crypto.randomUUID();
		const now = Date.now();
		ensureIntentsDoc(space);

		const intent: ExchangeIntent = {
			id,
			creatorDid: claims.did as string,
			creatorName: claims.username as string || 'Unknown',
			side,
			tokenId,
			fiatCurrency,
			tokenAmountMin,
			tokenAmountMax,
			rateType,
			rateFixed: body.rateFixed,
			rateMarketBps: body.rateMarketBps,
			paymentMethods: body.paymentMethods,
			isStandingOrder: body.isStandingOrder || false,
			autoAccept: body.autoAccept || false,
			allowInstitutionalFallback: body.allowInstitutionalFallback || false,
			minCounterpartyReputation: body.minCounterpartyReputation,
			preferredCounterparties: body.preferredCounterparties,
			status: 'active',
			createdAt: now,
			expiresAt: body.expiresAt,
		};

		ss().changeDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space), 'create exchange intent', (d) => {
			d.intents[id] = intent as any;
		});

		const doc = ss().getDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space))!;
		return c.json(doc.intents[id], 201);
	});

	// ── PATCH /api/exchange/intent/:id — Update/cancel intent ──

	routes.patch('/api/exchange/intent/:id', async (c) => {
		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Authentication required' }, 401);
		let claims;
		try { claims = await verifyToken(token); } catch { return c.json({ error: 'Invalid token' }, 401); }

		const space = c.req.param('space') || 'demo';
		const id = c.req.param('id');
		const body = await c.req.json();

		ensureIntentsDoc(space);
		const doc = ss().getDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space))!;
		const intent = doc.intents[id];
		if (!intent) return c.json({ error: 'Intent not found' }, 404);
		if (intent.creatorDid !== claims.did) return c.json({ error: 'Not your intent' }, 403);

		ss().changeDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space), 'update exchange intent', (d) => {
			const i = d.intents[id];
			if (body.status === 'cancelled') i.status = 'cancelled' as any;
			if (body.tokenAmountMin != null) i.tokenAmountMin = body.tokenAmountMin;
			if (body.tokenAmountMax != null) i.tokenAmountMax = body.tokenAmountMax;
			if (body.rateFixed != null) i.rateFixed = body.rateFixed as any;
			if (body.rateMarketBps != null) i.rateMarketBps = body.rateMarketBps as any;
			if (body.paymentMethods) i.paymentMethods = body.paymentMethods as any;
			if (body.minCounterpartyReputation != null) i.minCounterpartyReputation = body.minCounterpartyReputation as any;
		});

		const updated = ss().getDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space))!;
		return c.json(updated.intents[id]);
	});

	// ── GET /api/exchange/intents — Order book (active) ──

	routes.get('/api/exchange/intents', (c) => {
		const space = c.req.param('space') || 'demo';
		ensureIntentsDoc(space);
		const doc = ss().getDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space))!;
		let intents = Object.values(doc.intents).filter(i => i.status === 'active');

		// Optional filters
		const tokenId = c.req.query('tokenId');
		const fiatCurrency = c.req.query('fiatCurrency');
		const side = c.req.query('side');
		if (tokenId) intents = intents.filter(i => i.tokenId === tokenId);
		if (fiatCurrency) intents = intents.filter(i => i.fiatCurrency === fiatCurrency);
		if (side) intents = intents.filter(i => i.side === side);

		return c.json({ intents });
	});

	// ── GET /api/exchange/intents/mine — My intents ──

	routes.get('/api/exchange/intents/mine', async (c) => {
		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Authentication required' }, 401);
		let claims;
		try { claims = await verifyToken(token); } catch { return c.json({ error: 'Invalid token' }, 401); }

		const space = c.req.param('space') || 'demo';
		ensureIntentsDoc(space);
		const doc = ss().getDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space))!;
		const intents = Object.values(doc.intents).filter(i => i.creatorDid === claims.did);
		return c.json({ intents });
	});

	// ── GET /api/exchange/matches — Pending matches for me ──

	routes.get('/api/exchange/matches', async (c) => {
		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Authentication required' }, 401);
		let claims;
		try { claims = await verifyToken(token); } catch { return c.json({ error: 'Invalid token' }, 401); }

		const space = c.req.param('space') || 'demo';
		ensureTradesDoc(space);
		const doc = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;

		const matches = Object.values(doc.trades).filter(t =>
			t.status === 'proposed' &&
			(t.buyerDid === claims.did || t.sellerDid === claims.did),
		);

		return c.json({ matches });
	});

	// ── POST /api/exchange/matches/:id/accept — Accept match ──

	routes.post('/api/exchange/matches/:id/accept', async (c) => {
		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Authentication required' }, 401);
		let claims;
		try { claims = await verifyToken(token); } catch { return c.json({ error: 'Invalid token' }, 401); }

		const space = c.req.param('space') || 'demo';
		const id = c.req.param('id');
		ensureTradesDoc(space);

		const doc = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		const trade = doc.trades[id];
		if (!trade) return c.json({ error: 'Trade not found' }, 404);
		if (trade.status !== 'proposed') return c.json({ error: `Trade status is ${trade.status}` }, 400);
		if (trade.buyerDid !== claims.did && trade.sellerDid !== claims.did) {
			return c.json({ error: 'Not a party to this trade' }, 403);
		}

		ss().changeDoc<ExchangeTradesDoc>(exchangeTradesDocId(space), 'accept match', (d) => {
			d.trades[id].acceptances[claims.did as string] = true as any;
		});

		// Check if both accepted
		const updated = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		const updatedTrade = updated.trades[id];
		const allAccepted = updatedTrade.acceptances[updatedTrade.buyerDid] &&
			updatedTrade.acceptances[updatedTrade.sellerDid];

		if (allAccepted) {
			// Lock escrow
			ss().changeDoc<ExchangeTradesDoc>(exchangeTradesDocId(space), 'mark accepted', (d) => {
				d.trades[id].status = 'accepted' as any;
			});

			// Mark intents as matched
			ss().changeDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space), 'mark intents matched', (d) => {
				if (d.intents[updatedTrade.buyIntentId]) d.intents[updatedTrade.buyIntentId].status = 'matched' as any;
				if (d.intents[updatedTrade.sellIntentId]) d.intents[updatedTrade.sellIntentId].status = 'matched' as any;
			});

			const freshDoc = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
			const lockResult = lockEscrow(freshDoc.trades[id], ss(), space);
			if (!lockResult.success) {
				// Revert
				ss().changeDoc<ExchangeTradesDoc>(exchangeTradesDocId(space), 'revert failed escrow', (d) => {
					d.trades[id].status = 'proposed' as any;
				});
				return c.json({ error: `Escrow failed: ${lockResult.error}` }, 400);
			}
		}

		const final = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		return c.json(final.trades[id]);
	});

	// ── POST /api/exchange/matches/:id/reject — Reject match ──

	routes.post('/api/exchange/matches/:id/reject', async (c) => {
		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Authentication required' }, 401);
		let claims;
		try { claims = await verifyToken(token); } catch { return c.json({ error: 'Invalid token' }, 401); }

		const space = c.req.param('space') || 'demo';
		const id = c.req.param('id');
		ensureTradesDoc(space);

		const doc = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		const trade = doc.trades[id];
		if (!trade) return c.json({ error: 'Trade not found' }, 404);
		if (trade.status !== 'proposed') return c.json({ error: `Trade status is ${trade.status}` }, 400);

		ss().changeDoc<ExchangeTradesDoc>(exchangeTradesDocId(space), 'reject match', (d) => {
			d.trades[id].status = 'cancelled' as any;
		});

		return c.json({ ok: true });
	});

	// ── POST /api/exchange/trades/:id/fiat-sent — Buyer marks fiat sent ──

	routes.post('/api/exchange/trades/:id/fiat-sent', async (c) => {
		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Authentication required' }, 401);
		let claims;
		try { claims = await verifyToken(token); } catch { return c.json({ error: 'Invalid token' }, 401); }

		const space = c.req.param('space') || 'demo';
		const id = c.req.param('id');
		ensureTradesDoc(space);

		const doc = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		const trade = doc.trades[id];
		if (!trade) return c.json({ error: 'Trade not found' }, 404);
		if (trade.buyerDid !== claims.did) return c.json({ error: 'Only buyer can mark fiat sent' }, 403);
		if (trade.status !== 'escrow_locked') return c.json({ error: `Expected escrow_locked, got ${trade.status}` }, 400);

		ss().changeDoc<ExchangeTradesDoc>(exchangeTradesDocId(space), 'buyer marked fiat sent', (d) => {
			d.trades[id].status = 'fiat_sent' as any;
		});

		const updated = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		return c.json(updated.trades[id]);
	});

	// ── POST /api/exchange/trades/:id/confirm — Seller confirms fiat received ──

	routes.post('/api/exchange/trades/:id/confirm', async (c) => {
		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Authentication required' }, 401);
		let claims;
		try { claims = await verifyToken(token); } catch { return c.json({ error: 'Invalid token' }, 401); }

		const space = c.req.param('space') || 'demo';
		const id = c.req.param('id');
		ensureTradesDoc(space);

		const doc = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		const trade = doc.trades[id];
		if (!trade) return c.json({ error: 'Trade not found' }, 404);
		if (trade.sellerDid !== claims.did) return c.json({ error: 'Only seller can confirm fiat receipt' }, 403);
		if (trade.status !== 'fiat_sent') return c.json({ error: `Expected fiat_sent, got ${trade.status}` }, 400);

		ss().changeDoc<ExchangeTradesDoc>(exchangeTradesDocId(space), 'seller confirmed fiat', (d) => {
			d.trades[id].status = 'fiat_confirmed' as any;
		});

		const freshDoc = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		const result = releaseEscrow(freshDoc.trades[id], ss(), space);
		if (!result.success) {
			return c.json({ error: `Release failed: ${result.error}` }, 500);
		}

		const final = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		return c.json(final.trades[id]);
	});

	// ── POST /api/exchange/trades/:id/dispute — Raise dispute ──

	routes.post('/api/exchange/trades/:id/dispute', async (c) => {
		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Authentication required' }, 401);
		let claims;
		try { claims = await verifyToken(token); } catch { return c.json({ error: 'Invalid token' }, 401); }

		const space = c.req.param('space') || 'demo';
		const id = c.req.param('id');
		const body = await c.req.json();
		ensureTradesDoc(space);

		const doc = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		const trade = doc.trades[id];
		if (!trade) return c.json({ error: 'Trade not found' }, 404);
		if (trade.buyerDid !== claims.did && trade.sellerDid !== claims.did) {
			return c.json({ error: 'Not a party to this trade' }, 403);
		}
		if (!['escrow_locked', 'fiat_sent', 'fiat_confirmed'].includes(trade.status)) {
			return c.json({ error: `Cannot dispute in status ${trade.status}` }, 400);
		}

		ss().changeDoc<ExchangeTradesDoc>(exchangeTradesDocId(space), 'raise dispute', (d) => {
			d.trades[id].status = 'disputed' as any;
			if (body.reason) d.trades[id].disputeReason = body.reason as any;
		});

		// Track dispute in reputation
		ensureReputationDoc(space);
		ss().changeDoc<ExchangeReputationDoc>(exchangeReputationDocId(space), 'track dispute', (d) => {
			const disputerDid = claims.did as string;
			if (!d.records[disputerDid]) {
				d.records[disputerDid] = {
					did: disputerDid, tradesCompleted: 0, tradesCancelled: 0,
					disputesRaised: 0, disputesLost: 0, totalVolumeBase: 0,
					avgConfirmTimeMs: 0, score: 50, badges: [],
				} as any;
			}
			d.records[disputerDid].disputesRaised = (d.records[disputerDid].disputesRaised + 1) as any;
		});

		const updated = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		return c.json(updated.trades[id]);
	});

	// ── POST /api/exchange/trades/:id/resolve — Admin resolve dispute ──

	routes.post('/api/exchange/trades/:id/resolve', async (c) => {
		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Authentication required' }, 401);
		let claims;
		try { claims = await verifyToken(token); } catch { return c.json({ error: 'Invalid token' }, 401); }

		// TODO: proper admin check — for MVP, any authenticated user can resolve
		const space = c.req.param('space') || 'demo';
		const id = c.req.param('id');
		const body = await c.req.json();
		ensureTradesDoc(space);
		ensureReputationDoc(space);

		const doc = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		const trade = doc.trades[id];
		if (!trade) return c.json({ error: 'Trade not found' }, 404);
		if (trade.status !== 'disputed') return c.json({ error: `Expected disputed, got ${trade.status}` }, 400);

		const resolution = body.resolution as 'released_to_buyer' | 'returned_to_seller';
		if (resolution !== 'released_to_buyer' && resolution !== 'returned_to_seller') {
			return c.json({ error: 'resolution must be released_to_buyer or returned_to_seller' }, 400);
		}

		const ok = resolveDispute(trade, resolution, ss(), space);
		if (!ok) return c.json({ error: 'Resolution failed' }, 500);

		const final = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		return c.json(final.trades[id]);
	});

	// ── POST /api/exchange/trades/:id/message — Trade chat ──

	routes.post('/api/exchange/trades/:id/message', async (c) => {
		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Authentication required' }, 401);
		let claims;
		try { claims = await verifyToken(token); } catch { return c.json({ error: 'Invalid token' }, 401); }

		const space = c.req.param('space') || 'demo';
		const id = c.req.param('id');
		const body = await c.req.json();
		ensureTradesDoc(space);

		const doc = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		const trade = doc.trades[id];
		if (!trade) return c.json({ error: 'Trade not found' }, 404);
		if (trade.buyerDid !== claims.did && trade.sellerDid !== claims.did) {
			return c.json({ error: 'Not a party to this trade' }, 403);
		}
		if (!body.text?.trim()) return c.json({ error: 'text required' }, 400);

		const msgId = crypto.randomUUID();
		const MAX_CHAT_MESSAGES = 200;
		ss().changeDoc<ExchangeTradesDoc>(exchangeTradesDocId(space), 'trade chat message', (d) => {
			if (!d.trades[id].chatMessages) d.trades[id].chatMessages = [] as any;
			(d.trades[id].chatMessages as any[]).push({
				id: msgId,
				senderDid: claims.did,
				senderName: claims.username || 'Unknown',
				text: body.text.trim(),
				timestamp: Date.now(),
			});
			// Cap chat history to prevent unbounded CRDT growth
			const msgs = d.trades[id].chatMessages as any[];
			if (msgs.length > MAX_CHAT_MESSAGES) {
				msgs.splice(0, msgs.length - MAX_CHAT_MESSAGES);
			}
		});

		const updated = ss().getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space))!;
		return c.json(updated.trades[id]);
	});

	// ── POST /api/exchange/pool — Add Liquidity ──

	routes.post('/api/exchange/pool', async (c) => {
		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Authentication required' }, 401);
		let claims;
		try { claims = await verifyToken(token); } catch { return c.json({ error: 'Invalid token' }, 401); }

		const space = c.req.param('space') || 'demo';
		const body = await c.req.json();
		const { tokenId, fiatCurrency, tokenAmount, fiatAmount, spreadBps, paymentMethods } = body;

		if (!VALID_TOKENS.includes(tokenId)) return c.json({ error: `tokenId must be: ${VALID_TOKENS.join(', ')}` }, 400);
		if (!VALID_FIATS.includes(fiatCurrency)) return c.json({ error: `fiatCurrency must be: ${VALID_FIATS.join(', ')}` }, 400);
		if (typeof tokenAmount !== 'number' || tokenAmount <= 0) return c.json({ error: 'tokenAmount must be positive' }, 400);
		if (typeof fiatAmount !== 'number' || fiatAmount <= 0) return c.json({ error: 'fiatAmount must be positive' }, 400);
		if (typeof spreadBps !== 'number' || spreadBps < 0 || spreadBps > 1000) return c.json({ error: 'spreadBps must be 0-1000' }, 400);
		if (!paymentMethods?.length) return c.json({ error: 'At least one payment method required' }, 400);

		const positionId = crypto.randomUUID();
		const buyIntentId = crypto.randomUUID();
		const sellIntentId = crypto.randomUUID();
		const now = Date.now();
		const creatorDid = claims.did as string;
		const creatorName = claims.username as string || 'Unknown';

		// Create paired standing orders — buy at market-spreadBps, sell at market+spreadBps
		ensureIntentsDoc(space);
		const buyIntent: ExchangeIntent = {
			id: buyIntentId, creatorDid, creatorName,
			side: 'buy', tokenId, fiatCurrency,
			tokenAmountMin: 1_000_000, // 1 token minimum per fill
			tokenAmountMax: tokenAmount,
			rateType: 'market_plus_bps', rateMarketBps: spreadBps,
			paymentMethods, isStandingOrder: true, autoAccept: true,
			allowInstitutionalFallback: false, status: 'active',
			createdAt: now,
		};
		const sellIntent: ExchangeIntent = {
			id: sellIntentId, creatorDid, creatorName,
			side: 'sell', tokenId, fiatCurrency,
			tokenAmountMin: 1_000_000,
			tokenAmountMax: tokenAmount,
			rateType: 'market_plus_bps', rateMarketBps: spreadBps,
			paymentMethods, isStandingOrder: true, autoAccept: true,
			allowInstitutionalFallback: false, status: 'active',
			createdAt: now,
		};

		ss().changeDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space), 'create LP paired intents', (d) => {
			d.intents[buyIntentId] = buyIntent as any;
			d.intents[sellIntentId] = sellIntent as any;
		});

		// Create the liquidity position
		const position: LiquidityPosition = {
			id: positionId, creatorDid, creatorName,
			tokenId, fiatCurrency,
			tokenCommitted: tokenAmount, tokenRemaining: tokenAmount,
			fiatCommitted: fiatAmount, fiatRemaining: fiatAmount,
			spreadBps, paymentMethods,
			buyIntentId, sellIntentId,
			feesEarnedToken: 0, feesEarnedFiat: 0, tradesMatched: 0,
			status: 'active', createdAt: now, updatedAt: now,
		};

		ensurePoolsDoc(space);
		ss().changeDoc<ExchangePoolsDoc>(exchangePoolsDocId(space), 'create liquidity position', (d) => {
			d.positions[positionId] = position as any;
		});

		const doc = ss().getDoc<ExchangePoolsDoc>(exchangePoolsDocId(space))!;
		return c.json(doc.positions[positionId], 201);
	});

	// ── GET /api/exchange/pools — List all active positions ──

	routes.get('/api/exchange/pools', (c) => {
		const space = c.req.param('space') || 'demo';
		ensurePoolsDoc(space);
		const doc = ss().getDoc<ExchangePoolsDoc>(exchangePoolsDocId(space))!;
		const positions = Object.values(doc.positions).filter(p => p.status === 'active');
		return c.json({ positions });
	});

	// ── GET /api/exchange/pools/mine — My positions ──

	routes.get('/api/exchange/pools/mine', async (c) => {
		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Authentication required' }, 401);
		let claims;
		try { claims = await verifyToken(token); } catch { return c.json({ error: 'Invalid token' }, 401); }

		const space = c.req.param('space') || 'demo';
		ensurePoolsDoc(space);
		const doc = ss().getDoc<ExchangePoolsDoc>(exchangePoolsDocId(space))!;
		const positions = Object.values(doc.positions).filter(p => p.creatorDid === claims.did);
		return c.json({ positions });
	});

	// ── PATCH /api/exchange/pool/:id — Update or withdraw ──

	routes.patch('/api/exchange/pool/:id', async (c) => {
		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Authentication required' }, 401);
		let claims;
		try { claims = await verifyToken(token); } catch { return c.json({ error: 'Invalid token' }, 401); }

		const space = c.req.param('space') || 'demo';
		const id = c.req.param('id');
		const body = await c.req.json();

		ensurePoolsDoc(space);
		const doc = ss().getDoc<ExchangePoolsDoc>(exchangePoolsDocId(space))!;
		const position = doc.positions[id];
		if (!position) return c.json({ error: 'Position not found' }, 404);
		if (position.creatorDid !== claims.did) return c.json({ error: 'Not your position' }, 403);

		const now = Date.now();

		// Handle status changes
		if (body.status === 'paused' || body.status === 'withdrawn') {
			// Cancel linked intents
			ensureIntentsDoc(space);
			ss().changeDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space), `LP ${body.status}: cancel linked intents`, (d) => {
				if (d.intents[position.buyIntentId]) d.intents[position.buyIntentId].status = 'cancelled' as any;
				if (d.intents[position.sellIntentId]) d.intents[position.sellIntentId].status = 'cancelled' as any;
			});
		}

		ss().changeDoc<ExchangePoolsDoc>(exchangePoolsDocId(space), 'update liquidity position', (d) => {
			const p = d.positions[id];
			if (body.status) p.status = body.status as any;
			if (body.spreadBps != null) p.spreadBps = body.spreadBps as any;
			if (body.paymentMethods) p.paymentMethods = body.paymentMethods as any;
			if (body.tokenCommitted != null) {
				const delta = body.tokenCommitted - p.tokenCommitted;
				p.tokenCommitted = body.tokenCommitted as any;
				p.tokenRemaining = Math.max(0, p.tokenRemaining + delta) as any;
			}
			if (body.fiatCommitted != null) {
				const delta = body.fiatCommitted - p.fiatCommitted;
				p.fiatCommitted = body.fiatCommitted as any;
				p.fiatRemaining = Math.max(0, p.fiatRemaining + delta) as any;
			}
			p.updatedAt = now as any;
		});

		// If updating spread or payment methods, also update linked intents
		if ((body.spreadBps != null || body.paymentMethods) && body.status !== 'paused' && body.status !== 'withdrawn') {
			ensureIntentsDoc(space);
			ss().changeDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space), 'update LP linked intents', (d) => {
				for (const intentId of [position.buyIntentId, position.sellIntentId]) {
					const intent = d.intents[intentId];
					if (!intent) continue;
					if (body.spreadBps != null) intent.rateMarketBps = body.spreadBps as any;
					if (body.paymentMethods) intent.paymentMethods = body.paymentMethods as any;
				}
			});
		}

		const updated = ss().getDoc<ExchangePoolsDoc>(exchangePoolsDocId(space))!;
		return c.json(updated.positions[id]);
	});

	// ── GET /api/exchange/reputation/:did — Reputation lookup ──

	routes.get('/api/exchange/reputation/:did', (c) => {
		const space = c.req.param('space') || 'demo';
		const did = c.req.param('did');
		ensureReputationDoc(space);
		const doc = ss().getDoc<ExchangeReputationDoc>(exchangeReputationDocId(space))!;
		return c.json(getReputation(did, doc));
	});

	// ── GET /api/exchange/rate/:token/:fiat — Live rate ──

	routes.get('/api/exchange/rate/:token/:fiat', async (c) => {
		const tokenId = c.req.param('token') as TokenId;
		const fiat = c.req.param('fiat') as FiatCurrency;

		if (!VALID_TOKENS.includes(tokenId)) return c.json({ error: 'Invalid token' }, 400);
		if (!VALID_FIATS.includes(fiat)) return c.json({ error: 'Invalid fiat currency' }, 400);

		const rate = await getExchangeRate(tokenId, fiat);
		return c.json({ tokenId, fiatCurrency: fiat, rate });
	});

	return routes;
}

// ── Solver Cron ──

let _solverInterval: ReturnType<typeof setInterval> | null = null;

/**
 * Start the periodic solver cron. Runs every 60s.
 */
export function startSolverCron(getSyncServer: () => SyncServer | null) {
	if (_solverInterval) return;

	_solverInterval = setInterval(async () => {
		const syncServer = getSyncServer();
		if (!syncServer) return;

		// Get all spaces that have exchange intents
		const allDocIds = syncServer.listDocs();
		const spaces = allDocIds
			.filter(id => id.endsWith(':rexchange:intents'))
			.map(id => id.split(':')[0]);

		for (const space of spaces) {
			try {
				const intentsDoc = syncServer.getDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space));
				const reputationDoc = syncServer.getDoc<ExchangeReputationDoc>(exchangeReputationDocId(space));
				if (!intentsDoc || !reputationDoc) continue;

				const activeIntents = Object.values(intentsDoc.intents).filter(i => i.status === 'active');
				const hasBuys = activeIntents.some(i => i.side === 'buy');
				const hasSells = activeIntents.some(i => i.side === 'sell');
				if (!hasBuys || !hasSells) continue;

				// Run solver
				const matches = await solveExchange(intentsDoc, reputationDoc);
				if (matches.length === 0) continue;

				// Create trade entries for proposed matches
				const tradesDocId = exchangeTradesDocId(space);
				let tradesDoc = syncServer.getDoc<ExchangeTradesDoc>(tradesDocId);
				if (!tradesDoc) {
					tradesDoc = Automerge.change(Automerge.init<ExchangeTradesDoc>(), 'init exchange trades', (d) => {
						const init = exchangeTradesSchema.init();
						Object.assign(d, init);
						d.meta.spaceSlug = space;
					});
					syncServer.setDoc(tradesDocId, tradesDoc);
				}

				// Only create trades for new matches (avoid duplicates)
				const existingPairs = new Set(
					Object.values(tradesDoc.trades)
						.filter(t => t.status === 'proposed' || t.status === 'accepted' || t.status === 'escrow_locked' || t.status === 'fiat_sent')
						.map(t => `${t.buyIntentId}:${t.sellIntentId}`),
				);

				for (const match of matches) {
					const pairKey = `${match.buyIntentId}:${match.sellIntentId}`;
					if (existingPairs.has(pairKey)) continue;

					const tradeId = crypto.randomUUID();
					syncServer.changeDoc<ExchangeTradesDoc>(tradesDocId, 'solver: propose trade', (d) => {
						d.trades[tradeId] = {
							id: tradeId,
							...match,
							chatMessages: [],
							createdAt: Date.now(),
						} as any;
					});

					// If both parties have autoAccept, immediately accept and lock escrow
					const created = syncServer.getDoc<ExchangeTradesDoc>(tradesDocId)!;
					const trade = created.trades[tradeId];
					if (trade.acceptances[trade.buyerDid] && trade.acceptances[trade.sellerDid]) {
						syncServer.changeDoc<ExchangeTradesDoc>(tradesDocId, 'auto-accept trade', (d) => {
							d.trades[tradeId].status = 'accepted' as any;
						});
						syncServer.changeDoc<ExchangeIntentsDoc>(exchangeIntentsDocId(space), 'auto-match intents', (d) => {
							if (d.intents[trade.buyIntentId]) d.intents[trade.buyIntentId].status = 'matched' as any;
							if (d.intents[trade.sellIntentId]) d.intents[trade.sellIntentId].status = 'matched' as any;
						});

						const freshDoc = syncServer.getDoc<ExchangeTradesDoc>(tradesDocId)!;
						lockEscrow(freshDoc.trades[tradeId], syncServer, space);
					}

					console.log(`[rExchange] Solver proposed trade ${tradeId}: ${match.buyerName} ↔ ${match.sellerName} for ${match.tokenAmount} ${match.tokenId}`);
				}

				// Sweep timeouts
				sweepTimeouts(syncServer, space);
			} catch (e) {
				console.warn(`[rExchange] Solver error for space ${space}:`, e);
			}
		}
	}, 60_000);
}

export function stopSolverCron() {
	if (_solverInterval) {
		clearInterval(_solverInterval);
		_solverInterval = null;
	}
}
