/**
 * Atomic settlement via saga pattern.
 *
 * When all members accept a solver result:
 * 1. Validate all acceptances
 * 2. Confirm escrow burns for need intents
 * 3. Create Connection entries in CommitmentsDoc
 * 4. Create Task entries in TasksDoc
 * 5. Update intent statuses → 'settled'
 * 6. Update reputation entries
 * 7. Update skill curve supply/demand counters
 *
 * On failure at any step: reverse all confirmed burns and abort.
 */

import * as Automerge from '@automerge/automerge';
import type { SyncServer } from '../../server/local-first/sync-server';
import { confirmBurn, reverseBurn } from '../../server/token-service';
import { commitmentsDocId, tasksDocId, weavingDocId } from './schemas';
import type { CommitmentsDoc, TasksDoc, WeavingDoc, Skill } from './schemas';
import { weavingSchema } from './schemas';
import { boardDocId, createTaskItem } from '../rtasks/schemas';
import type { BoardDoc } from '../rtasks/schemas';
import {
	intentsDocId, solverResultsDocId,
	skillCurvesDocId, reputationDocId,
} from './schemas-intent';
import type {
	IntentsDoc, SolverResultsDoc,
	SkillCurvesDoc, ReputationDoc,
	SolverResult, Intent,
} from './schemas-intent';
import { reputationKey, DEFAULT_REPUTATION } from './reputation';
import { recalculateCurve } from './skill-curve';

// ── Settlement ──

export interface SettlementOutcome {
	success: boolean;
	resultId: string;
	connectionsCreated: number;
	tasksCreated: number;
	error?: string;
}

/**
 * Settle a solver result — atomic multi-doc update with saga rollback.
 */
export async function settleResult(
	resultId: string,
	space: string,
	syncServer: SyncServer,
): Promise<SettlementOutcome> {
	const outcome: SettlementOutcome = {
		success: false,
		resultId,
		connectionsCreated: 0,
		tasksCreated: 0,
	};

	// Load all docs
	const solverDoc = syncServer.getDoc<SolverResultsDoc>(solverResultsDocId(space));
	const intentsDoc = syncServer.getDoc<IntentsDoc>(intentsDocId(space));

	if (!solverDoc || !intentsDoc) {
		outcome.error = 'Required documents not found';
		return outcome;
	}

	const result = solverDoc.results[resultId];
	if (!result) {
		outcome.error = `Solver result ${resultId} not found`;
		return outcome;
	}

	// Step 1: Validate all acceptances
	const allAccepted = result.members.every(m => result.acceptances[m] === true);
	if (!allAccepted) {
		outcome.error = 'Not all members have accepted';
		return outcome;
	}

	if (result.status !== 'proposed' && result.status !== 'accepted') {
		outcome.error = `Result status is ${result.status}, expected proposed or accepted`;
		return outcome;
	}

	// Gather intents
	const intents = result.intents
		.map(id => intentsDoc.intents[id])
		.filter(Boolean);

	if (intents.length !== result.intents.length) {
		outcome.error = 'Some intents in the result no longer exist';
		return outcome;
	}

	// Step 2: Confirm escrow burns for need intents (with saga tracking)
	const confirmedEscrows: string[] = [];
	const needIntents = intents.filter(i => i.type === 'need' && i.escrowTxId);

	for (const need of needIntents) {
		const success = confirmBurn('cusdc', need.escrowTxId!);
		if (!success) {
			// Rollback all previously confirmed burns
			for (const txId of confirmedEscrows) {
				reverseBurn('cusdc', txId);
			}
			outcome.error = `Failed to confirm escrow for intent ${need.id} (txId: ${need.escrowTxId})`;
			return outcome;
		}
		confirmedEscrows.push(need.escrowTxId!);
	}

	// Step 3 & 4: Create connections and tasks
	const now = Date.now();
	const offerIntents = intents.filter(i => i.type === 'offer');
	const needIntentsAll = intents.filter(i => i.type === 'need');

	// Create a task in rTasks board + weaving overlay
	const taskId = crypto.randomUUID();
	const taskSkills = [...new Set(intents.map(i => i.skill))];
	const taskNeeds: Record<string, number> = {};
	for (const need of needIntentsAll) {
		taskNeeds[need.skill] = (taskNeeds[need.skill] || 0) + need.hours;
	}

	const taskTitle = `Collaboration: ${taskSkills.map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' + ')}`;
	const taskDesc = `Auto-generated from solver result. Members: ${result.members.length}`;

	// Try to create in rTasks board
	const boardSlug = space; // default board
	const bDocId = boardDocId(space, boardSlug);
	const boardDoc = syncServer.getDoc<BoardDoc>(bDocId);

	if (boardDoc) {
		const taskItem = createTaskItem(taskId, space, taskTitle, { description: taskDesc });
		syncServer.changeDoc<BoardDoc>(bDocId, 'settlement: create rTasks item', (d) => {
			d.tasks[taskId] = taskItem as any;
		});
	} else {
		// Fallback: create in legacy TasksDoc
		syncServer.changeDoc<TasksDoc>(tasksDocId(space), 'settlement: create task (legacy)', (d) => {
			d.tasks[taskId] = {
				id: taskId, name: taskTitle, description: taskDesc,
				needs: taskNeeds, links: [], notes: `Settled from solver result ${resultId}`,
			} as any;
		});
	}
	outcome.tasksCreated = 1;

	// Ensure WeavingDoc and create overlay + connections
	let wDoc = syncServer.getDoc<WeavingDoc>(weavingDocId(space));
	if (!wDoc) {
		wDoc = Automerge.change(Automerge.init<WeavingDoc>(), 'init weaving', (d) => {
			const init = weavingSchema.init();
			Object.assign(d, init);
			d.meta.spaceSlug = space;
		});
		syncServer.setDoc(weavingDocId(space), wDoc);
	}

	// Create weaving overlay
	syncServer.changeDoc<WeavingDoc>(weavingDocId(space), 'settlement: create overlay', (d) => {
		d.weavingOverlays[taskId] = {
			rtasksId: taskId,
			needs: taskNeeds,
			canvasX: 400,
			canvasY: 150,
			notes: `Settled from solver result ${resultId}`,
			links: [],
			intentFrameId: resultId,
		} as any;
	});

	// Create connections in WeavingDoc (offer → task)
	syncServer.changeDoc<WeavingDoc>(weavingDocId(space), 'settlement: create connections', (d) => {
		for (const offer of offerIntents) {
			const connId = crypto.randomUUID();
			d.connections[connId] = {
				id: connId,
				fromCommitmentId: offer.id,
				toTaskId: taskId,
				skill: offer.skill,
				hours: offer.hours,
				status: 'committed',
			} as any;
			outcome.connectionsCreated++;
		}
	});

	// Create commitment entries for each offer intent (so they appear in the pool)
	syncServer.changeDoc<CommitmentsDoc>(commitmentsDocId(space), 'settlement: create commitments', (d) => {
		for (const offer of offerIntents) {
			if (!d.items[offer.id]) {
				d.items[offer.id] = {
					id: offer.id,
					memberName: offer.memberName,
					hours: offer.hours,
					skill: offer.skill,
					desc: offer.description,
					createdAt: offer.createdAt,
					intentId: offer.id,
					status: 'settled',
				} as any;
			}
		}
	});

	// Step 5: Update intent statuses
	syncServer.changeDoc<IntentsDoc>(intentsDocId(space), 'settlement: mark intents settled', (d) => {
		for (const intent of intents) {
			if (d.intents[intent.id]) {
				d.intents[intent.id].status = 'settled';
			}
		}
	});

	// Step 6: Update solver result status
	syncServer.changeDoc<SolverResultsDoc>(solverResultsDocId(space), 'settlement: mark result settled', (d) => {
		if (d.results[resultId]) {
			d.results[resultId].status = 'settled';
		}
	});

	// Step 7: Update reputation (initial entry — actual ratings come later)
	syncServer.changeDoc<ReputationDoc>(reputationDocId(space), 'settlement: init reputation', (d) => {
		for (const intent of intents) {
			const key = reputationKey(intent.memberId, intent.skill);
			if (!d.entries[key]) {
				d.entries[key] = {
					memberId: intent.memberId,
					skill: intent.skill,
					score: DEFAULT_REPUTATION,
					completedHours: 0,
					ratings: [],
				} as any;
			}
			// Add completed hours
			d.entries[key].completedHours += intent.hours;
		}
	});

	// Step 8: Update skill curves
	updateSkillCurves(space, syncServer);

	outcome.success = true;
	return outcome;
}

/**
 * Recalculate skill curves from current active intents.
 */
export function updateSkillCurves(space: string, syncServer: SyncServer): void {
	const intentsDoc = syncServer.getDoc<IntentsDoc>(intentsDocId(space));
	if (!intentsDoc) return;

	const activeIntents = Object.values(intentsDoc.intents)
		.filter(i => i.status === 'active');

	// Aggregate supply/demand per skill
	const skills: Skill[] = ['facilitation', 'design', 'tech', 'outreach', 'logistics'];
	const aggregates = new Map<Skill, { supply: number; demand: number }>();

	for (const skill of skills) {
		aggregates.set(skill, { supply: 0, demand: 0 });
	}

	for (const intent of activeIntents) {
		const agg = aggregates.get(intent.skill);
		if (!agg) continue;
		if (intent.type === 'offer') agg.supply += intent.hours;
		else agg.demand += intent.hours;
	}

	syncServer.changeDoc<SkillCurvesDoc>(skillCurvesDocId(space), 'update skill curves', (d) => {
		for (const [skill, agg] of aggregates) {
			const updated = recalculateCurve(skill, agg.supply, agg.demand);
			const now = Date.now();
			if (!d.curves[skill]) {
				d.curves[skill] = {
					...updated,
					history: [{ price: updated.currentPrice, timestamp: now }],
				} as any;
			} else {
				d.curves[skill].supplyHours = updated.supplyHours;
				d.curves[skill].demandHours = updated.demandHours;
				d.curves[skill].currentPrice = updated.currentPrice;
				// Append to history (keep last 100 entries)
				if (!d.curves[skill].history) d.curves[skill].history = [] as any;
				d.curves[skill].history.push({ price: updated.currentPrice, timestamp: now } as any);
				if (d.curves[skill].history.length > 100) {
					d.curves[skill].history.splice(0, d.curves[skill].history.length - 100);
				}
			}
		}
	});
}

/**
 * Check if a result is ready for settlement (all members accepted).
 */
export function isReadyForSettlement(result: SolverResult): boolean {
	if (result.status !== 'proposed' && result.status !== 'accepted') return false;
	return result.members.every(m => result.acceptances[m] === true);
}
