/**
 * Tasks module — kanban workspace boards.
 *
 * Multi-tenant collaborative workspace with drag-and-drop kanban,
 * configurable statuses, and activity logging.
 *
 * All persistence uses Automerge documents via SyncServer —
 * no PostgreSQL dependency.
 */

import { Hono } from "hono";
import * as Automerge from '@automerge/automerge';
import { renderShell } from "../../server/shell";
import { getModuleInfoList } from "../../shared/module";
import type { RSpaceModule, SpaceLifecycleContext } from "../../shared/module";
import { verifyEncryptIDToken, extractToken } from "@encryptid/sdk/server";
import { renderLanding } from "./landing";
import type { SyncServer } from '../../server/local-first/sync-server';
import { boardSchema, boardDocId, createTaskItem } from './schemas';
import type { BoardDoc, TaskItem, BoardMeta } from './schemas';

const routes = new Hono();

// ── Local-first helpers ──
let _syncServer: SyncServer | null = null;

/**
 * Lazily create the board Automerge doc if it doesn't exist yet.
 * Returns the current (immutable) doc snapshot.
 */
function ensureDoc(space: string, boardId?: string): BoardDoc {
	const id = boardDocId(space, boardId ?? space);
	let doc = _syncServer!.getDoc<BoardDoc>(id);
	if (!doc) {
		doc = Automerge.change(Automerge.init<BoardDoc>(), 'init board', (d) => {
			const init = boardSchema.init();
			d.meta = init.meta;
			d.meta.spaceSlug = space;
			d.board = init.board;
			d.board.id = boardId ?? space;
			d.board.slug = boardId ?? space;
			d.board.name = space;
			d.tasks = {};
		});
		_syncServer!.setDoc(id, doc);
	}
	return doc;
}

/**
 * Get all board doc IDs for a given space.
 */
function getBoardDocIds(space: string): string[] {
	return _syncServer!.getDocIds().filter((id) => id.startsWith(`${space}:tasks:boards:`));
}

/**
 * Seed demo data if no boards exist for the given space.
 */
function seedDemoIfEmpty(space: string = 'rspace-dev') {
	if (!_syncServer) return;
	// Check if this space already has tasks boards
	const spaceWorkDocs = _syncServer!.getDocIds().filter((id) => id.startsWith(`${space}:tasks:boards:`));
	if (spaceWorkDocs.length > 0) return;

	const docId = boardDocId(space, space);

	const doc = Automerge.change(Automerge.init<BoardDoc>(), 'seed demo board', (d) => {
		const now = Date.now();
		d.meta = { module: 'tasks', collection: 'boards', version: 1, spaceSlug: space, createdAt: now };
		d.board = {
			id: space,
			name: 'rSpace Development',
			slug: space,
			description: 'Building the cosmolocal r* ecosystem',
			icon: null,
			ownerDid: 'did:demo:seed',
			statuses: ['TODO', 'IN_PROGRESS', 'REVIEW', 'DONE'],
			labels: [],
			createdAt: now,
			updatedAt: now,
		};
		d.tasks = {};

		const seedTasks: Array<{ title: string; status: string; priority: string; labels: string[]; sort: number }> = [
			{ title: "Add dark mode toggle to settings page", status: "TODO", priority: "MEDIUM", labels: ["feature"], sort: 0 },
			{ title: "Write API documentation for rPubs endpoints", status: "TODO", priority: "LOW", labels: ["docs"], sort: 1 },
			{ title: "Investigate slow PDF generation on large documents", status: "TODO", priority: "HIGH", labels: ["bug"], sort: 2 },
			{ title: "Implement file search and filtering in rFiles", status: "IN_PROGRESS", priority: "HIGH", labels: ["feature"], sort: 0 },
			{ title: "Set up SMTP relay for transactional notifications", status: "IN_PROGRESS", priority: "MEDIUM", labels: ["chore"], sort: 1 },
			{ title: "Add PDF export to rNotes notebooks", status: "REVIEW", priority: "MEDIUM", labels: ["feature"], sort: 0 },
			{ title: "Fix conviction score decay calculation in rVote", status: "REVIEW", priority: "HIGH", labels: ["bug"], sort: 1 },
			{ title: "Deploy EncryptID passkey authentication", status: "DONE", priority: "URGENT", labels: ["feature"], sort: 0 },
			{ title: "Set up Cloudflare tunnel for all r* domains", status: "DONE", priority: "HIGH", labels: ["chore"], sort: 1 },
			{ title: "Create cosmolocal provider directory with 6 printers", status: "DONE", priority: "MEDIUM", labels: ["feature"], sort: 2 },
			{ title: "Migrate email from Resend to self-hosted Mailcow", status: "DONE", priority: "MEDIUM", labels: ["chore"], sort: 3 },
		];

		for (const t of seedTasks) {
			const taskId = crypto.randomUUID();
			d.tasks[taskId] = createTaskItem(taskId, space, t.title, {
				status: t.status,
				priority: t.priority,
				labels: t.labels,
				sortOrder: t.sort,
				createdBy: 'did:demo:seed',
			});
		}
	});

	_syncServer!.setDoc(docId, doc);
	console.log(`[Tasks] Demo data seeded for "${space}": 1 board, 11 tasks`);
}

// ── API: Spaces (Boards) ──

// GET /api/spaces — list workspaces (boards)
routes.get("/api/spaces", async (c) => {
	const allIds = _syncServer!.getDocIds().filter((id) => id.includes(':tasks:boards:'));
	const rows = allIds.map((docId) => {
		const doc = _syncServer!.getDoc<BoardDoc>(docId);
		if (!doc) return null;
		const taskCount = Object.keys(doc.tasks).length;
		return {
			id: doc.board.id,
			name: doc.board.name,
			slug: doc.board.slug,
			description: doc.board.description,
			icon: doc.board.icon,
			owner_did: doc.board.ownerDid,
			statuses: doc.board.statuses,
			created_at: new Date(doc.board.createdAt).toISOString(),
			updated_at: new Date(doc.board.updatedAt).toISOString(),
			member_count: 0,
			task_count: taskCount,
		};
	}).filter(Boolean);

	// Sort by created_at DESC
	rows.sort((a, b) => (b!.created_at > a!.created_at ? 1 : -1));
	return c.json(rows);
});

// POST /api/spaces — create workspace (board)
routes.post("/api/spaces", async (c) => {
	const token = extractToken(c.req.raw.headers);
	if (!token) return c.json({ error: "Authentication required" }, 401);
	let claims;
	try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }

	const body = await c.req.json();
	const { name, description, icon } = body;
	if (!name?.trim()) return c.json({ error: "Name required" }, 400);

	const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
	const docId = boardDocId(slug, slug);

	// Check if board already exists
	const existing = _syncServer!.getDoc<BoardDoc>(docId);
	if (existing) return c.json({ error: "Space with this slug already exists" }, 409);

	const now = Date.now();
	const doc = Automerge.change(Automerge.init<BoardDoc>(), 'create board', (d) => {
		d.meta = { module: 'tasks', collection: 'boards', version: 1, spaceSlug: slug, createdAt: now };
		d.board = {
			id: slug,
			name: name.trim(),
			slug,
			description: description || '',
			icon: icon || null,
			ownerDid: claims.sub,
			statuses: ['TODO', 'IN_PROGRESS', 'DONE'],
			labels: [],
			createdAt: now,
			updatedAt: now,
		};
		d.tasks = {};
	});
	_syncServer!.setDoc(docId, doc);

	return c.json({
		id: slug,
		name: name.trim(),
		slug,
		description: description || null,
		icon: icon || null,
		owner_did: claims.sub,
		statuses: ['TODO', 'IN_PROGRESS', 'DONE'],
		created_at: new Date(now).toISOString(),
		updated_at: new Date(now).toISOString(),
	}, 201);
});

// GET /api/spaces/:slug — workspace detail
routes.get("/api/spaces/:slug", async (c) => {
	const slug = c.req.param("slug");
	const docId = boardDocId(slug, slug);
	const doc = _syncServer!.getDoc<BoardDoc>(docId);
	if (!doc) return c.json({ error: "Space not found" }, 404);

	return c.json({
		id: doc.board.id,
		name: doc.board.name,
		slug: doc.board.slug,
		description: doc.board.description,
		icon: doc.board.icon,
		owner_did: doc.board.ownerDid,
		statuses: doc.board.statuses,
		labels: doc.board.labels,
		created_at: new Date(doc.board.createdAt).toISOString(),
		updated_at: new Date(doc.board.updatedAt).toISOString(),
	});
});

// ── API: Tasks ──

// GET /api/spaces/:slug/tasks — list tasks in workspace
routes.get("/api/spaces/:slug/tasks", async (c) => {
	const slug = c.req.param("slug");
	const doc = ensureDoc(slug);

	const tasks = Object.values(doc.tasks).map((t) => ({
		id: t.id,
		space_id: t.spaceId,
		title: t.title,
		description: t.description,
		status: t.status,
		priority: t.priority,
		labels: t.labels,
		assignee_id: t.assigneeId,
		assignee_name: null,
		created_by: t.createdBy,
		sort_order: t.sortOrder,
		created_at: new Date(t.createdAt).toISOString(),
		updated_at: new Date(t.updatedAt).toISOString(),
	}));

	// Sort by status, then sort_order, then created_at DESC
	const statusOrder: Record<string, number> = {};
	doc.board.statuses.forEach((s, i) => { statusOrder[s] = i; });
	tasks.sort((a, b) => {
		const sa = statusOrder[a.status] ?? 999;
		const sb = statusOrder[b.status] ?? 999;
		if (sa !== sb) return sa - sb;
		if (a.sort_order !== b.sort_order) return a.sort_order - b.sort_order;
		return b.created_at > a.created_at ? 1 : -1;
	});

	return c.json(tasks);
});

// POST /api/spaces/:slug/tasks — create task
routes.post("/api/spaces/:slug/tasks", async (c) => {
	const token = extractToken(c.req.raw.headers);
	if (!token) return c.json({ error: "Authentication required" }, 401);
	let claims;
	try { claims = await verifyEncryptIDToken(token); } catch { return c.json({ error: "Invalid token" }, 401); }

	const slug = c.req.param("slug");
	const body = await c.req.json();
	const { title, description, status, priority, labels } = body;
	if (!title?.trim()) return c.json({ error: "Title required" }, 400);

	const doc = ensureDoc(slug);
	const taskStatus = status || doc.board.statuses[0] || "TODO";
	const taskId = crypto.randomUUID();
	const now = Date.now();

	const docId = boardDocId(slug, slug);
	_syncServer!.changeDoc<BoardDoc>(docId, `Create task ${taskId}`, (d) => {
		d.tasks[taskId] = createTaskItem(taskId, slug, title.trim(), {
			description: description || '',
			status: taskStatus,
			priority: priority || 'MEDIUM',
			labels: labels || [],
			createdBy: claims.sub,
		});
	});

	return c.json({
		id: taskId,
		space_id: slug,
		title: title.trim(),
		description: description || null,
		status: taskStatus,
		priority: priority || "MEDIUM",
		labels: labels || [],
		assignee_id: null,
		created_by: claims.sub,
		sort_order: 0,
		created_at: new Date(now).toISOString(),
		updated_at: new Date(now).toISOString(),
	}, 201);
});

// PATCH /api/tasks/:id — update task (status change, assignment, etc.)
routes.patch("/api/tasks/:id", async (c) => {
	// Optional auth — track who updated
	const token = extractToken(c.req.raw.headers);
	let updatedBy: string | null = null;
	if (token) {
		try { const claims = await verifyEncryptIDToken(token); updatedBy = claims.sub; } catch {}
	}

	const id = c.req.param("id");
	const body = await c.req.json();
	const { title, description, status, priority, labels, sort_order, assignee_id } = body;

	// Check that at least one field is being updated
	if (title === undefined && description === undefined && status === undefined &&
		priority === undefined && labels === undefined && sort_order === undefined &&
		assignee_id === undefined) {
		return c.json({ error: "No fields to update" }, 400);
	}

	// Find which board doc contains this task
	const allBoardIds = _syncServer!.getDocIds().filter((docId) => docId.includes(':tasks:boards:'));
	let targetDocId: string | null = null;
	for (const docId of allBoardIds) {
		const doc = _syncServer!.getDoc<BoardDoc>(docId);
		if (doc && doc.tasks[id]) {
			targetDocId = docId;
			break;
		}
	}
	if (!targetDocId) return c.json({ error: "Task not found" }, 404);

	_syncServer!.changeDoc<BoardDoc>(targetDocId, `Update task ${id}`, (d) => {
		const task = d.tasks[id];
		if (!task) return;
		if (title !== undefined) task.title = title;
		if (description !== undefined) task.description = description;
		if (status !== undefined) task.status = status;
		if (priority !== undefined) task.priority = priority;
		if (labels !== undefined) task.labels = labels;
		if (sort_order !== undefined) task.sortOrder = sort_order;
		if (assignee_id !== undefined) task.assigneeId = assignee_id || null;
		task.updatedAt = Date.now();
	});

	// Return the updated task
	const updatedDoc = _syncServer!.getDoc<BoardDoc>(targetDocId)!;
	const task = updatedDoc.tasks[id];
	return c.json({
		id: task.id,
		space_id: task.spaceId,
		title: task.title,
		description: task.description,
		status: task.status,
		priority: task.priority,
		labels: task.labels,
		assignee_id: task.assigneeId,
		created_by: task.createdBy,
		sort_order: task.sortOrder,
		created_at: new Date(task.createdAt).toISOString(),
		updated_at: new Date(task.updatedAt).toISOString(),
	});
});

// DELETE /api/tasks/:id
routes.delete("/api/tasks/:id", async (c) => {
	const id = c.req.param("id");

	// Find which board doc contains this task
	const allBoardIds = _syncServer!.getDocIds().filter((docId) => docId.includes(':tasks:boards:'));
	let targetDocId: string | null = null;
	for (const docId of allBoardIds) {
		const doc = _syncServer!.getDoc<BoardDoc>(docId);
		if (doc && doc.tasks[id]) {
			targetDocId = docId;
			break;
		}
	}
	if (!targetDocId) return c.json({ error: "Task not found" }, 404);

	_syncServer!.changeDoc<BoardDoc>(targetDocId, `Delete task ${id}`, (d) => {
		delete d.tasks[id];
	});

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

// ── API: Activity ──

// GET /api/spaces/:slug/activity — recent activity
// With Automerge, activity is tracked via document change history.
// Return an empty array for now; real activity can be derived from
// Automerge.getHistory() or a dedicated activity doc in the future.
routes.get("/api/spaces/:slug/activity", async (c) => {
	return c.json([]);
});

// ── Page route ──
routes.get("/", (c) => {
	const space = c.req.param("space") || "demo";
	const dataSpace = (c.get("effectiveSpace" as any) as string) || space;
	return c.html(renderShell({
		title: `${space} — Tasks | rSpace`,
		moduleId: "rtasks",
		spaceSlug: space,
		modules: getModuleInfoList(),
		theme: "dark",
		body: `<folk-tasks-board space="${space}"></folk-tasks-board>`,
		scripts: `<script type="module" src="/modules/rtasks/folk-tasks-board.js"></script>`,
		styles: `<link rel="stylesheet" href="/modules/rtasks/tasks.css">`,
	}));
});

export const tasksModule: RSpaceModule = {
	id: "rtasks",
	name: "rTasks",
	icon: "📋",
	description: "Kanban workspace boards for collaborative task management",
	scoping: { defaultScope: 'space', userConfigurable: false },
	docSchemas: [{ pattern: '{space}:tasks:boards:{boardId}', description: 'Kanban board with tasks', init: boardSchema.init }],
	routes,
	standaloneDomain: "rtasks.online",
	landingPage: renderLanding,
	seedTemplate: seedDemoIfEmpty,
	async onInit(ctx) {
		_syncServer = ctx.syncServer;
		seedDemoIfEmpty();
	},
	async onSpaceCreate(ctx: SpaceLifecycleContext) {
		if (!_syncServer) return;
		const docId = boardDocId(ctx.spaceSlug, ctx.spaceSlug);
		const doc = Automerge.init<BoardDoc>();
		const initialized = Automerge.change(doc, 'Init board', (d) => {
			d.meta = { module: 'tasks', collection: 'boards', version: 1, spaceSlug: ctx.spaceSlug, createdAt: Date.now() };
			d.board = { id: ctx.spaceSlug, name: ctx.spaceSlug, slug: ctx.spaceSlug, description: '', icon: null, ownerDid: ctx.ownerDID, statuses: ['TODO', 'IN_PROGRESS', 'DONE'], labels: [], createdAt: Date.now(), updatedAt: Date.now() };
			d.tasks = {};
		});
		_syncServer.setDoc(docId, initialized);
	},
	feeds: [
		{
			id: "task-activity",
			name: "Task Activity",
			kind: "data",
			description: "Task creation, status changes, and assignment updates",
			filterable: true,
		},
		{
			id: "board-summary",
			name: "Board Summary",
			kind: "data",
			description: "Kanban board state — counts by status column",
		},
	],
	acceptsFeeds: ["governance", "data"],
	outputPaths: [
		{ path: "projects", name: "Projects", icon: "📋", description: "Kanban project boards" },
		{ path: "tasks", name: "Tasks", icon: "✅", description: "Task cards across all boards" },
	],
};
