/**
 * P2P Exchange Settlement — escrow lifecycle with saga rollback.
 *
 * Escrow mechanism (reuses token-service):
 * 1. Lock:    burnTokensEscrow(tokenId, sellerDid, amount, 'p2p-'+tradeId) — seller's tokens escrowed
 * 2. Release: confirmBurn(tokenId, 'p2p-'+tradeId) + mintTokens(tokenId, buyerDid, amount) — net supply neutral
 * 3. Reverse: reverseBurn(tokenId, 'p2p-'+tradeId) — seller gets tokens back
 *
 * Timeout sweep: trades with status=fiat_sent past fiatConfirmDeadline → auto reverseBurn.
 * Disputes: admin calls resolve with resolution direction.
 */

import type { SyncServer } from '../../server/local-first/sync-server';
import {
	burnTokensEscrow, confirmBurn, reverseBurn,
	mintTokens, getTokenDoc, getBalance,
} from '../../server/token-service';
import {
	exchangeIntentsDocId, exchangeTradesDocId, exchangeReputationDocId, exchangePoolsDocId,
} from './schemas';
import type {
	ExchangeIntentsDoc, ExchangeTradesDoc, ExchangeReputationDoc, ExchangePoolsDoc,
	ExchangeTrade, TradeStatus,
} from './schemas';
import { updateReputationAfterTrade, calculateScore, computeBadges, hasStandingOrders } from './exchange-reputation';

// ── Constants ──

const FIAT_CONFIRM_TIMEOUT_MS = 24 * 60 * 60 * 1000; // 24 hours

// ── Escrow Lock ──

export interface LockResult {
	success: boolean;
	error?: string;
}

/**
 * Lock seller's tokens in escrow for a trade.
 */
export function lockEscrow(
	trade: ExchangeTrade,
	syncServer: SyncServer,
	space: string,
): LockResult {
	const doc = getTokenDoc(trade.tokenId);
	if (!doc) return { success: false, error: `Token ${trade.tokenId} not found` };

	// Check seller balance
	const balance = getBalance(doc, trade.sellerDid);
	if (balance < trade.tokenAmount) {
		return { success: false, error: `Insufficient balance: ${balance} < ${trade.tokenAmount}` };
	}

	const offRampId = `p2p-${trade.id}`;
	const success = burnTokensEscrow(
		trade.tokenId,
		trade.sellerDid,
		trade.sellerName,
		trade.tokenAmount,
		offRampId,
		`P2P escrow: ${trade.tokenAmount} ${trade.tokenId} for trade ${trade.id}`,
	);

	if (!success) {
		return { success: false, error: 'Failed to create escrow entry' };
	}

	// Update trade with escrow reference
	syncServer.changeDoc<ExchangeTradesDoc>(
		exchangeTradesDocId(space),
		'lock escrow',
		(d) => {
			if (d.trades[trade.id]) {
				d.trades[trade.id].escrowTxId = offRampId;
				d.trades[trade.id].status = 'escrow_locked' as any;
				d.trades[trade.id].fiatConfirmDeadline = (Date.now() + FIAT_CONFIRM_TIMEOUT_MS) as any;
			}
		},
	);

	return { success: true };
}

// ── Release (buyer confirmed fiat receipt by seller) ──

export interface ReleaseResult {
	success: boolean;
	error?: string;
}

/**
 * Release escrowed tokens to buyer after seller confirms fiat receipt.
 */
export function releaseEscrow(
	trade: ExchangeTrade,
	syncServer: SyncServer,
	space: string,
): ReleaseResult {
	if (!trade.escrowTxId) {
		return { success: false, error: 'No escrow reference on trade' };
	}

	// Confirm the burn (marks original burn as confirmed)
	const burnOk = confirmBurn(trade.tokenId, trade.escrowTxId);
	if (!burnOk) {
		return { success: false, error: 'Failed to confirm escrow burn' };
	}

	// Mint equivalent tokens to buyer (net supply neutral)
	const mintOk = mintTokens(
		trade.tokenId,
		trade.buyerDid,
		trade.buyerName,
		trade.tokenAmount,
		`P2P exchange: received from ${trade.sellerName} (trade ${trade.id})`,
		'rexchange',
	);

	if (!mintOk) {
		// Rollback: reverse the confirmed burn
		reverseBurn(trade.tokenId, trade.escrowTxId);
		return { success: false, error: 'Failed to mint tokens to buyer' };
	}

	const now = Date.now();

	// Update trade status
	syncServer.changeDoc<ExchangeTradesDoc>(
		exchangeTradesDocId(space),
		'release escrow — trade completed',
		(d) => {
			if (d.trades[trade.id]) {
				d.trades[trade.id].status = 'completed' as any;
				d.trades[trade.id].completedAt = now as any;
			}
		},
	);

	// Update intent statuses
	updateIntentsAfterTrade(trade, syncServer, space);

	// Update reputation for both parties
	const confirmTime = trade.fiatConfirmDeadline
		? FIAT_CONFIRM_TIMEOUT_MS - (trade.fiatConfirmDeadline - now)
		: 0;
	updateReputationForTrade(trade, confirmTime, syncServer, space);

	// Track LP fee accrual if trade matched an LP position
	updateLpPositionAfterTrade(trade, syncServer, space);

	return { success: true };
}

// ── Reverse (timeout or dispute resolution) ──

/**
 * Reverse escrow — return tokens to seller.
 */
export function reverseEscrow(
	trade: ExchangeTrade,
	reason: TradeStatus,
	syncServer: SyncServer,
	space: string,
): boolean {
	if (!trade.escrowTxId) return false;

	const ok = reverseBurn(trade.tokenId, trade.escrowTxId);
	if (!ok) return false;

	syncServer.changeDoc<ExchangeTradesDoc>(
		exchangeTradesDocId(space),
		`reverse escrow — ${reason}`,
		(d) => {
			if (d.trades[trade.id]) {
				d.trades[trade.id].status = reason as any;
			}
		},
	);

	// Re-activate intents if not standing orders
	reactivateIntents(trade, syncServer, space);

	return true;
}

// ── Dispute resolution ──

/**
 * Resolve a disputed trade. Admin decides: release to buyer or return to seller.
 */
export function resolveDispute(
	trade: ExchangeTrade,
	resolution: 'released_to_buyer' | 'returned_to_seller',
	syncServer: SyncServer,
	space: string,
): boolean {
	if (trade.status !== 'disputed') return false;

	syncServer.changeDoc<ExchangeTradesDoc>(
		exchangeTradesDocId(space),
		`resolve dispute — ${resolution}`,
		(d) => {
			if (d.trades[trade.id]) {
				d.trades[trade.id].resolution = resolution as any;
			}
		},
	);

	if (resolution === 'released_to_buyer') {
		const result = releaseEscrow(trade, syncServer, space);
		if (!result.success) return false;
		// Loser of dispute = seller
		updateDisputeLoser(trade.sellerDid, syncServer, space);
	} else {
		const ok = reverseEscrow(trade, 'resolved', syncServer, space);
		if (!ok) return false;
		// Loser of dispute = buyer
		updateDisputeLoser(trade.buyerDid, syncServer, space);
	}

	return true;
}

// ── Timeout sweep ──

/**
 * Check for timed-out trades and reverse their escrows.
 * Called periodically by the solver cron.
 */
export function sweepTimeouts(syncServer: SyncServer, space: string): number {
	const tradesDoc = syncServer.getDoc<ExchangeTradesDoc>(exchangeTradesDocId(space));
	if (!tradesDoc) return 0;

	const now = Date.now();
	let reversed = 0;

	for (const trade of Object.values(tradesDoc.trades)) {
		if (
			trade.status === 'fiat_sent' &&
			trade.fiatConfirmDeadline &&
			now > trade.fiatConfirmDeadline
		) {
			const ok = reverseEscrow(trade, 'timed_out', syncServer, space);
			if (ok) {
				reversed++;
				console.log(`[rExchange] Trade ${trade.id} timed out — escrow reversed`);
			}
		}
	}

	return reversed;
}

// ── Helpers ──

function updateIntentsAfterTrade(
	trade: ExchangeTrade,
	syncServer: SyncServer,
	space: string,
): void {
	syncServer.changeDoc<ExchangeIntentsDoc>(
		exchangeIntentsDocId(space),
		'update intents after trade completion',
		(d) => {
			const buyIntent = d.intents[trade.buyIntentId];
			const sellIntent = d.intents[trade.sellIntentId];

			if (buyIntent) {
				if (buyIntent.isStandingOrder) {
					// Standing order: reduce range and re-activate
					const newMin = Math.max(0, buyIntent.tokenAmountMin - trade.tokenAmount);
					const newMax = Math.max(0, buyIntent.tokenAmountMax - trade.tokenAmount);
					if (newMax > 0) {
						buyIntent.tokenAmountMin = newMin as any;
						buyIntent.tokenAmountMax = newMax as any;
						buyIntent.status = 'active' as any;
					} else {
						buyIntent.status = 'completed' as any;
					}
				} else {
					buyIntent.status = 'completed' as any;
				}
			}

			if (sellIntent) {
				if (sellIntent.isStandingOrder) {
					const newMin = Math.max(0, sellIntent.tokenAmountMin - trade.tokenAmount);
					const newMax = Math.max(0, sellIntent.tokenAmountMax - trade.tokenAmount);
					if (newMax > 0) {
						sellIntent.tokenAmountMin = newMin as any;
						sellIntent.tokenAmountMax = newMax as any;
						sellIntent.status = 'active' as any;
					} else {
						sellIntent.status = 'completed' as any;
					}
				} else {
					sellIntent.status = 'completed' as any;
				}
			}
		},
	);
}

function reactivateIntents(
	trade: ExchangeTrade,
	syncServer: SyncServer,
	space: string,
): void {
	syncServer.changeDoc<ExchangeIntentsDoc>(
		exchangeIntentsDocId(space),
		'reactivate intents after trade reversal',
		(d) => {
			const buyIntent = d.intents[trade.buyIntentId];
			const sellIntent = d.intents[trade.sellIntentId];
			if (buyIntent && buyIntent.status === 'matched') buyIntent.status = 'active' as any;
			if (sellIntent && sellIntent.status === 'matched') sellIntent.status = 'active' as any;
		},
	);
}

function updateReputationForTrade(
	trade: ExchangeTrade,
	confirmTimeMs: number,
	syncServer: SyncServer,
	space: string,
): void {
	const repDocId = exchangeReputationDocId(space);

	syncServer.changeDoc<ExchangeReputationDoc>(repDocId, 'update reputation after trade', (d) => {
		for (const did of [trade.buyerDid, trade.sellerDid]) {
			if (!d.records[did]) {
				d.records[did] = {
					did,
					tradesCompleted: 0,
					tradesCancelled: 0,
					disputesRaised: 0,
					disputesLost: 0,
					totalVolumeBase: 0,
					avgConfirmTimeMs: 0,
					score: 50,
					badges: [],
				} as any;
			}

			const rec = d.records[did];
			const newCompleted = rec.tradesCompleted + 1;
			const prevTotal = rec.avgConfirmTimeMs * rec.tradesCompleted;
			const newAvg = newCompleted > 0 ? (prevTotal + confirmTimeMs) / newCompleted : 0;

			rec.tradesCompleted = newCompleted as any;
			rec.totalVolumeBase = (rec.totalVolumeBase + trade.tokenAmount) as any;
			rec.avgConfirmTimeMs = newAvg as any;

			// Recalculate score inline (can't call external fn inside Automerge mutator with complex logic)
			const totalTrades = rec.tradesCompleted + rec.tradesCancelled;
			const completionRate = totalTrades > 0 ? rec.tradesCompleted / totalTrades : 0.5;
			const disputeRate = totalTrades > 0 ? rec.disputesRaised / totalTrades : 0;
			const disputeLossRate = rec.disputesRaised > 0 ? rec.disputesLost / rec.disputesRaised : 0;
			const oneHour = 3600_000;
			const twentyFourHours = 86400_000;
			const speedScore = rec.avgConfirmTimeMs <= 0 ? 0.5
				: rec.avgConfirmTimeMs <= oneHour ? 1.0
				: Math.max(0, 1 - (rec.avgConfirmTimeMs - oneHour) / (twentyFourHours - oneHour));

			rec.score = Math.round(Math.max(0, Math.min(100,
				completionRate * 50 + (1 - disputeRate) * 25 + (1 - disputeLossRate) * 15 + speedScore * 10,
			))) as any;

			// Badges
			const badges: string[] = [];
			if (rec.tradesCompleted >= 5 && rec.score >= 70) badges.push('verified_seller');
			if (rec.totalVolumeBase >= 10_000_000_000) badges.push('top_trader');
			rec.badges = badges as any;
		}
	});
}

/**
 * After a trade completes, check if either intent belongs to an LP position.
 * If so, update fee earnings and remaining amounts on the position.
 */
function updateLpPositionAfterTrade(
	trade: ExchangeTrade,
	syncServer: SyncServer,
	space: string,
): void {
	const poolsDocId = exchangePoolsDocId(space);
	const poolsDoc = syncServer.getDoc<ExchangePoolsDoc>(poolsDocId);
	if (!poolsDoc) return;

	// Find position that owns either the buy or sell intent
	const position = Object.values(poolsDoc.positions).find(
		p => p.buyIntentId === trade.buyIntentId || p.sellIntentId === trade.sellIntentId,
	);
	if (!position || position.status !== 'active') return;

	syncServer.changeDoc<ExchangePoolsDoc>(poolsDocId, 'LP fee accrual after trade', (d) => {
		const p = d.positions[position.id];
		if (!p) return;

		p.tradesMatched = (p.tradesMatched + 1) as any;
		p.updatedAt = Date.now() as any;

		// Determine which side was filled
		if (position.sellIntentId === trade.sellIntentId) {
			// LP sold tokens → decrement tokenRemaining, earn fiat fee
			p.tokenRemaining = Math.max(0, p.tokenRemaining - trade.tokenAmount) as any;
			// Fee ≈ spread portion of the fiat amount: (spreadBps/10000) × fiatAmount
			const fiatFee = (p.spreadBps / 10000) * trade.fiatAmount;
			p.feesEarnedFiat = (p.feesEarnedFiat + fiatFee) as any;
		}
		if (position.buyIntentId === trade.buyIntentId) {
			// LP bought tokens → decrement fiatRemaining, earn token fee
			p.fiatRemaining = Math.max(0, p.fiatRemaining - trade.fiatAmount) as any;
			// Fee ≈ spread portion of the token amount: (spreadBps/10000) × tokenAmount
			const tokenFee = Math.round((p.spreadBps / 10000) * trade.tokenAmount);
			p.feesEarnedToken = (p.feesEarnedToken + tokenFee) as any;
		}

		// If either side depleted, pause the position
		if (p.tokenRemaining <= 0 || p.fiatRemaining <= 0) {
			p.status = 'paused' as any;
		}
	});
}

function updateDisputeLoser(
	loserDid: string,
	syncServer: SyncServer,
	space: string,
): void {
	syncServer.changeDoc<ExchangeReputationDoc>(
		exchangeReputationDocId(space),
		'update dispute loser',
		(d) => {
			if (d.records[loserDid]) {
				d.records[loserDid].disputesLost = (d.records[loserDid].disputesLost + 1) as any;
			}
		},
	);
}
