/**
 * Intent API routes — Hono sub-router for intent CRUD, solver, and settlement.
 *
 * All routes are relative to the module mount point (/:space/rtime/).
 */

import { Hono } from 'hono';
import * as Automerge from '@automerge/automerge';
import { verifyToken, extractToken } from '../../server/auth';
import { burnTokensEscrow, reverseBurn } from '../../server/token-service';
import type { SyncServer } from '../../server/local-first/sync-server';
import {
	intentsDocId, solverResultsDocId,
	skillCurvesDocId, reputationDocId,
	intentsSchema, solverResultsSchema,
	skillCurvesSchema, reputationSchema,
} from './schemas-intent';
import type {
	IntentsDoc, SolverResultsDoc,
	SkillCurvesDoc, ReputationDoc,
	Intent, IntentStatus,
} from './schemas-intent';
import type { Skill } from './schemas';
import { getSkillPrice, calculateIntentCost, getAllSkillPrices, getSkillCurveConfig } from './skill-curve';
import { getMemberSkillReputation, getMemberOverallReputation } from './reputation';
import { solve } from './solver';
import { settleResult, isReadyForSettlement, updateSkillCurves } from './settlement';
import { notify } from '../../server/notification-service';

const VALID_SKILLS: Skill[] = ['facilitation', 'design', 'tech', 'outreach', 'logistics'];

/**
 * Create the intent routes sub-router.
 * Requires a SyncServer reference (passed from mod.ts onInit).
 */
export function createIntentRoutes(getSyncServer: () => SyncServer | null): Hono {
	const routes = new Hono();

	// ── Helpers ──

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

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

	function ensureSolverResultsDoc(space: string): SolverResultsDoc {
		const docId = solverResultsDocId(space);
		let doc = syncServer().getDoc<SolverResultsDoc>(docId);
		if (!doc) {
			doc = Automerge.change(Automerge.init<SolverResultsDoc>(), 'init solver results', (d) => {
				const init = solverResultsSchema.init();
				Object.assign(d, init);
				d.meta.spaceSlug = space;
			});
			syncServer().setDoc(docId, doc);
		}
		return doc;
	}

	function ensureSkillCurvesDoc(space: string): SkillCurvesDoc {
		const docId = skillCurvesDocId(space);
		let doc = syncServer().getDoc<SkillCurvesDoc>(docId);
		if (!doc) {
			doc = Automerge.change(Automerge.init<SkillCurvesDoc>(), 'init skill curves', (d) => {
				const init = skillCurvesSchema.init();
				Object.assign(d, init);
				d.meta.spaceSlug = space;
			});
			syncServer().setDoc(docId, doc);
		}
		return doc;
	}

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

	function truncateDid(did: string): string {
		if (!did || did.length < 20) return did;
		return did.slice(0, 12) + '…' + did.slice(-6);
	}

	async function requireAuth(headers: Headers): Promise<{ did: string; username: string } | null> {
		const token = extractToken(headers);
		if (!token) return null;
		try {
			const claims = await verifyToken(token);
			return { did: claims.sub, username: (claims as any).username || truncateDid(claims.sub) };
		} catch {
			return null;
		}
	}

	// ── Intent CRUD ──

	// POST /api/intent — Create a new intent
	routes.post('/api/intent', async (c) => {
		const auth = await requireAuth(c.req.raw.headers);
		if (!auth) return c.json({ error: 'Authentication required' }, 401);

		const space = c.req.param('space') || 'demo';
		const body = await c.req.json();
		const { type, skill, hours, description, minReputation, maxPrice, preferredMembers, expiresAt } = body;

		// Validate
		if (!type || !['offer', 'need'].includes(type)) {
			return c.json({ error: 'type must be "offer" or "need"' }, 400);
		}
		if (!skill || !VALID_SKILLS.includes(skill)) {
			return c.json({ error: `skill must be one of: ${VALID_SKILLS.join(', ')}` }, 400);
		}
		if (!hours || hours < 1 || hours > 100) {
			return c.json({ error: 'hours must be between 1 and 100' }, 400);
		}

		ensureIntentsDoc(space);
		ensureSkillCurvesDoc(space);

		const id = crypto.randomUUID();
		const now = Date.now();
		let escrowTxId: string | undefined;

		// For need intents: lock tokens in escrow
		if (type === 'need') {
			const curvesDoc = syncServer().getDoc<SkillCurvesDoc>(skillCurvesDocId(space))!;
			const cost = calculateIntentCost(skill, hours, curvesDoc);

			const offRampId = `intent-${id}`;
			const success = burnTokensEscrow(
				'cusdc',
				auth.did,
				auth.username,
				cost,
				offRampId,
				`Intent escrow: ${hours}h ${skill} @ ${getSkillPrice(skill, curvesDoc)} tokens/h`,
			);

			if (!success) {
				return c.json({ error: 'Insufficient balance for escrow' }, 402);
			}
			escrowTxId = offRampId;
		}

		// Write intent
		syncServer().changeDoc<IntentsDoc>(intentsDocId(space), 'create intent', (d) => {
			d.intents[id] = {
				id,
				memberId: auth.did,
				memberName: auth.username,
				type,
				skill,
				hours: Math.max(1, Math.min(100, hours)),
				description: description || '',
				status: 'active',
				createdAt: now,
				...(minReputation != null ? { minReputation } : {}),
				...(maxPrice != null ? { maxPrice } : {}),
				...(preferredMembers?.length ? { preferredMembers } : {}),
				...(escrowTxId ? { escrowTxId } : {}),
				...(expiresAt ? { expiresAt } : {}),
			} as any;
		});

		// Update skill curves
		updateSkillCurves(space, syncServer());

		const doc = syncServer().getDoc<IntentsDoc>(intentsDocId(space))!;
		return c.json(doc.intents[id], 201);
	});

	// PATCH /api/intent/:id — Update or withdraw an intent
	routes.patch('/api/intent/:id', async (c) => {
		const auth = await requireAuth(c.req.raw.headers);
		if (!auth) return c.json({ error: 'Authentication required' }, 401);

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

		ensureIntentsDoc(space);
		const doc = syncServer().getDoc<IntentsDoc>(intentsDocId(space))!;
		const intent = doc.intents[id];

		if (!intent) return c.json({ error: 'Intent not found' }, 404);
		if (intent.memberId !== auth.did) return c.json({ error: 'Not your intent' }, 403);
		if (intent.status !== 'active') return c.json({ error: `Cannot modify intent in status: ${intent.status}` }, 400);

		// Handle withdrawal
		if (body.status === 'withdrawn') {
			// Reverse escrow if need intent
			if (intent.escrowTxId) {
				reverseBurn('cusdc', intent.escrowTxId);
			}

			syncServer().changeDoc<IntentsDoc>(intentsDocId(space), 'withdraw intent', (d) => {
				d.intents[id].status = 'withdrawn';
			});

			updateSkillCurves(space, syncServer());
			return c.json({ ok: true, status: 'withdrawn' });
		}

		// Handle field updates
		syncServer().changeDoc<IntentsDoc>(intentsDocId(space), 'update intent', (d) => {
			if (body.description !== undefined) d.intents[id].description = body.description;
			if (body.minReputation !== undefined) d.intents[id].minReputation = body.minReputation;
			if (body.maxPrice !== undefined) d.intents[id].maxPrice = body.maxPrice;
			if (body.preferredMembers !== undefined) d.intents[id].preferredMembers = body.preferredMembers;
		});

		const updated = syncServer().getDoc<IntentsDoc>(intentsDocId(space))!;
		return c.json(updated.intents[id]);
	});

	// GET /api/intents — List active intents for space
	routes.get('/api/intents', (c) => {
		const space = c.req.param('space') || 'demo';
		ensureIntentsDoc(space);
		const doc = syncServer().getDoc<IntentsDoc>(intentsDocId(space))!;
		const intents = Object.values(doc.intents);
		return c.json({ intents });
	});

	// GET /api/intents/:memberId — List member's intents
	routes.get('/api/intents/:memberId', (c) => {
		const space = c.req.param('space') || 'demo';
		const memberId = c.req.param('memberId');
		ensureIntentsDoc(space);
		const doc = syncServer().getDoc<IntentsDoc>(intentsDocId(space))!;
		const intents = Object.values(doc.intents).filter(i => i.memberId === memberId);
		return c.json({ intents });
	});

	// ── Solver ──

	// GET /api/solver-results — Get current solver recommendations
	routes.get('/api/solver-results', (c) => {
		const space = c.req.param('space') || 'demo';
		ensureSolverResultsDoc(space);
		const doc = syncServer().getDoc<SolverResultsDoc>(solverResultsDocId(space))!;
		const results = Object.values(doc.results)
			.filter(r => r.status === 'proposed' || r.status === 'accepted');
		return c.json({ results });
	});

	// POST /api/solver/run — Manual solver trigger
	routes.post('/api/solver/run', async (c) => {
		const auth = await requireAuth(c.req.raw.headers);
		if (!auth) return c.json({ error: 'Authentication required' }, 401);

		const space = c.req.param('space') || 'demo';
		const resultCount = runSolver(space);

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

	// POST /api/solver-results/:id/accept — Member accepts a recommendation
	routes.post('/api/solver-results/:id/accept', async (c) => {
		const auth = await requireAuth(c.req.raw.headers);
		if (!auth) return c.json({ error: 'Authentication required' }, 401);

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

		ensureSolverResultsDoc(space);
		const doc = syncServer().getDoc<SolverResultsDoc>(solverResultsDocId(space))!;
		const result = doc.results[resultId];

		if (!result) return c.json({ error: 'Result not found' }, 404);
		if (!result.members.includes(auth.did)) return c.json({ error: 'Not a participant' }, 403);
		if (result.status !== 'proposed' && result.status !== 'accepted') {
			return c.json({ error: `Cannot accept result in status: ${result.status}` }, 400);
		}

		syncServer().changeDoc<SolverResultsDoc>(solverResultsDocId(space), 'accept result', (d) => {
			d.results[resultId].acceptances[auth.did] = true;
		});

		// Notify other members that this member accepted
		const otherMembers = result.members.filter(m => m !== auth.did);
		for (const memberDid of otherMembers) {
			notify({
				userDid: memberDid,
				category: 'module',
				eventType: 'commitment_accepted',
				title: `Collaboration accepted`,
				body: `${auth.username} accepted the ${result.totalHours}hr collaboration`,
				spaceSlug: space,
				moduleId: 'rtime',
				actionUrl: `/rtime`,
				actorDid: auth.did,
				metadata: { resultId },
			}).catch(() => {});
		}

		// Check if all accepted → auto-settle
		const updated = syncServer().getDoc<SolverResultsDoc>(solverResultsDocId(space))!;
		const updatedResult = updated.results[resultId];

		if (isReadyForSettlement(updatedResult)) {
			// Mark as accepted first
			syncServer().changeDoc<SolverResultsDoc>(solverResultsDocId(space), 'mark accepted', (d) => {
				d.results[resultId].status = 'accepted';
			});

			const outcome = await settleResult(resultId, space, syncServer());
			return c.json({ ok: true, settled: outcome.success, outcome });
		}

		return c.json({
			ok: true,
			settled: false,
			acceptances: updatedResult.acceptances,
			remaining: updatedResult.members.filter(m => !updatedResult.acceptances[m]),
		});
	});

	// POST /api/solver-results/:id/reject — Member rejects a recommendation
	routes.post('/api/solver-results/:id/reject', async (c) => {
		const auth = await requireAuth(c.req.raw.headers);
		if (!auth) return c.json({ error: 'Authentication required' }, 401);

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

		ensureSolverResultsDoc(space);
		const doc = syncServer().getDoc<SolverResultsDoc>(solverResultsDocId(space))!;
		const result = doc.results[resultId];

		if (!result) return c.json({ error: 'Result not found' }, 404);
		if (!result.members.includes(auth.did)) return c.json({ error: 'Not a participant' }, 403);

		syncServer().changeDoc<SolverResultsDoc>(solverResultsDocId(space), 'reject result', (d) => {
			d.results[resultId].status = 'rejected';
		});

		// Notify other members that this member declined
		const otherMembers = result.members.filter(m => m !== auth.did);
		for (const memberDid of otherMembers) {
			notify({
				userDid: memberDid,
				category: 'module',
				eventType: 'commitment_declined',
				title: `Collaboration declined`,
				body: `${auth.username} declined the ${result.totalHours}hr collaboration`,
				spaceSlug: space,
				moduleId: 'rtime',
				actionUrl: `/rtime`,
				actorDid: auth.did,
				metadata: { resultId },
			}).catch(() => {});
		}

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

	// ── Skill Curves ──

	// GET /api/skill-curves — Get current skill prices
	routes.get('/api/skill-curves', (c) => {
		const space = c.req.param('space') || 'demo';
		ensureSkillCurvesDoc(space);
		const doc = syncServer().getDoc<SkillCurvesDoc>(skillCurvesDocId(space))!;
		const prices = getAllSkillPrices(doc);
		return c.json({ curves: Object.values(doc.curves), prices, config: getSkillCurveConfig() });
	});

	// ── Reputation ──

	// GET /api/reputation/:memberId — Get member reputation
	routes.get('/api/reputation/:memberId', (c) => {
		const space = c.req.param('space') || 'demo';
		const memberId = c.req.param('memberId');
		ensureReputationDoc(space);
		const doc = syncServer().getDoc<ReputationDoc>(reputationDocId(space))!;

		const entries = Object.values(doc.entries).filter(e => e.memberId === memberId);
		const overall = getMemberOverallReputation(memberId, doc);

		return c.json({ memberId, overall, skills: entries });
	});

	// ── Internal: Run solver ──

	function runSolver(space: string): number {
		ensureIntentsDoc(space);
		ensureSolverResultsDoc(space);
		ensureSkillCurvesDoc(space);
		ensureReputationDoc(space);

		const intentsDoc = syncServer().getDoc<IntentsDoc>(intentsDocId(space))!;
		const reputationDoc = syncServer().getDoc<ReputationDoc>(reputationDocId(space))!;
		const skillCurvesDoc = syncServer().getDoc<SkillCurvesDoc>(skillCurvesDocId(space))!;

		const candidates = solve(intentsDoc, reputationDoc, skillCurvesDoc);

		if (candidates.length === 0) return 0;

		// Clear old proposed results and write new ones
		syncServer().changeDoc<SolverResultsDoc>(solverResultsDocId(space), 'solver: write results', (d) => {
			// Remove stale proposed results
			for (const [id, result] of Object.entries(d.results)) {
				if (result.status === 'proposed') delete d.results[id];
			}

			// Add new results
			const now = Date.now();
			for (const candidate of candidates) {
				const id = crypto.randomUUID();
				d.results[id] = {
					id,
					...candidate,
					createdAt: now,
				} as any;
			}
		});

		console.log(`[rTime] Solver produced ${candidates.length} results for space "${space}"`);
		return candidates.length;
	}

	return routes;
}
