/**
 * rCred API routes — contribution recognition endpoints.
 */

import { Hono } from 'hono';
import * as Automerge from '@automerge/automerge';
import { verifyToken, extractToken } from '../../server/auth';
import { resolveCallerRole } from '../../server/spaces';
import type { SyncServer } from '../../server/local-first/sync-server';
import { getTokenDoc, getAllBalances, getAllTransfers } from '../../server/token-service';
import type { CredConfigDoc, CredScoresDoc, ContribGraphDoc } from './schemas';
import { scoresDocId, graphDocId, configDocId, configSchema, DEFAULT_WEIGHTS } from './schemas';
import { recomputeSpace, ensureConfigDoc } from './grain-engine';

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

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

	// ── GET /api/scores — leaderboard ──
	routes.get('/api/scores', (c) => {
		const space = c.req.param('space') || c.req.query('space') || '';
		if (!space) return c.json({ error: 'space required' }, 400);

		const doc = ss().getDoc<CredScoresDoc>(scoresDocId(space));
		if (!doc || !doc.scores) return c.json({ scores: [], totalCred: 0, computedAt: 0 });

		const scores = Object.values(doc.scores)
			.sort((a, b) => b.cred - a.cred)
			.map((s, i) => ({
				rank: i + 1,
				did: s.did,
				label: s.label,
				cred: s.cred,
				grainLifetime: s.grainLifetime,
				topModule: Object.entries(s.breakdown).sort(([, a], [, b]) => b - a)[0]?.[0] || '',
				lastActive: s.lastActive,
			}));

		return c.json({
			scores,
			totalCred: doc.totalCred,
			computedAt: doc.computedAt,
			epochId: doc.epochId,
		});
	});

	// ── GET /api/scores/:did — single contributor detail ──
	routes.get('/api/scores/:did', (c) => {
		const space = c.req.param('space') || c.req.query('space') || '';
		const did = c.req.param('did');
		if (!space || !did) return c.json({ error: 'space and did required' }, 400);

		const doc = ss().getDoc<CredScoresDoc>(scoresDocId(space));
		if (!doc?.scores) return c.json({ error: 'No scores found' }, 404);

		const score = doc.scores[did];
		if (!score) return c.json({ error: 'Contributor not found' }, 404);

		return c.json(score);
	});

	// ── GET /api/graph — contribution graph ──
	routes.get('/api/graph', (c) => {
		const space = c.req.param('space') || c.req.query('space') || '';
		if (!space) return c.json({ error: 'space required' }, 400);

		const doc = ss().getDoc<ContribGraphDoc>(graphDocId(space));
		if (!doc) return c.json({ nodes: {}, edges: {}, stats: null });

		return c.json({
			nodes: doc.nodes,
			edges: doc.edges,
			lastBuiltAt: doc.lastBuiltAt,
			stats: doc.stats,
		});
	});

	// ── GET /api/config — current weights + grain config ──
	routes.get('/api/config', (c) => {
		const space = c.req.param('space') || c.req.query('space') || '';
		if (!space) return c.json({ error: 'space required' }, 400);

		const config = ensureConfigDoc(space, ss());
		return c.json({
			weights: config.weights,
			grainPerEpoch: config.grainPerEpoch,
			epochLengthDays: config.epochLengthDays,
			slowFraction: config.slowFraction,
			fastFraction: config.fastFraction,
			dampingFactor: config.dampingFactor,
			lookbackDays: config.lookbackDays,
			enabled: config.enabled,
			lastEpochAt: config.lastEpochAt,
		});
	});

	// ── PUT /api/config — update weights (auth required) ──
	routes.put('/api/config', async (c) => {
		const space = c.req.param('space') || c.req.query('space') || '';
		if (!space) return c.json({ error: 'space required' }, 400);

		const token = extractToken(c.req.raw.headers);
		if (!token) return c.json({ error: 'Auth required' }, 401);
		const claims = await verifyToken(token);
		if (!claims) return c.json({ error: 'Invalid token' }, 401);

		const resolved = await resolveCallerRole(space, claims);
		if (!resolved || (resolved.role !== 'admin' && !resolved.isOwner)) {
			return c.json({ error: 'Admin access required' }, 403);
		}

		const body = await c.req.json<Partial<CredConfigDoc>>();
		const docId = configDocId(space);
		ensureConfigDoc(space, ss());

		ss().changeDoc<CredConfigDoc>(docId, 'Update rcred config', (d) => {
			if (body.weights) {
				for (const [key, val] of Object.entries(body.weights)) {
					if (d.weights[key] && typeof val.weight === 'number') {
						d.weights[key].weight = Math.max(0, Math.min(10, val.weight));
					}
				}
			}
			if (typeof body.grainPerEpoch === 'number') d.grainPerEpoch = body.grainPerEpoch;
			if (typeof body.epochLengthDays === 'number') d.epochLengthDays = body.epochLengthDays;
			if (typeof body.dampingFactor === 'number') d.dampingFactor = body.dampingFactor;
			if (typeof body.lookbackDays === 'number') d.lookbackDays = body.lookbackDays;
			if (typeof body.enabled === 'boolean') d.enabled = body.enabled;
		});

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

	// ── POST /api/recompute — trigger immediate recompute ──
	// Demo space: open access. Other spaces: require member+ auth.
	routes.post('/api/recompute', async (c) => {
		const space = c.req.param('space') || c.req.query('space') || '';
		if (!space) return c.json({ error: 'space required' }, 400);

		if (space !== 'demo') {
			const token = extractToken(c.req.raw.headers);
			if (!token) return c.json({ error: 'Auth required' }, 401);
			const claims = await verifyToken(token);
			if (!claims) return c.json({ error: 'Invalid token' }, 401);

			const resolved = await resolveCallerRole(space, claims);
			if (!resolved || resolved.role === 'viewer') {
				return c.json({ error: 'Membership required' }, 403);
			}
		}

		const result = recomputeSpace(space, ss());
		return c.json(result);
	});

	// ── GET /api/grain/balances — grain balances from token-service ──
	routes.get('/api/grain/balances', (c) => {
		const space = c.req.param('space') || c.req.query('space') || '';
		if (!space) return c.json({ error: 'space required' }, 400);

		const tokenId = `grain-${space}`;
		const tokenDoc = getTokenDoc(tokenId);
		if (!tokenDoc) return c.json({ balances: {} });

		const balances = getAllBalances(tokenDoc);
		return c.json({ tokenId, balances });
	});

	// ── GET /api/grain/history — grain ledger entries ──
	routes.get('/api/grain/history', (c) => {
		const space = c.req.param('space') || c.req.query('space') || '';
		if (!space) return c.json({ error: 'space required' }, 400);

		const tokenId = `grain-${space}`;
		const entries = getAllTransfers().filter(e => e.tokenId === tokenId);
		return c.json({ entries: entries.slice(-100) });
	});

	return routes;
}
