import { zValidator } from "@hono/zod-validator";
import { and, eq, inArray } from "drizzle-orm";
import { Hono } from "hono";
import type { db as prodDb } from "../../db/index.ts";
import {
	apiKeys,
	categories,
	items,
	oauthCodes,
	oauthTokens,
	settings,
	setupItems,
	setups,
	threadCandidates,
	threads,
	users,
} from "../../db/schema.ts";
import {
	changeEmailSchema,
	changePasswordSchema,
	deleteAccountSchema,
} from "../../shared/schemas.ts";
import { requireAuth } from "../middleware/auth.ts";
import { logtoClient } from "../services/logto.service.ts";

type Db = typeof prodDb;
type Env = { Variables: { db?: any; userId?: number } };

const app = new Hono<Env>();

// All account management routes require authentication (per T-28-05)
app.use("*", requireAuth);

// ── Helper ──────────────────────────────────────────────────────────

async function getLogtoSub(db: Db, userId: number): Promise<string> {
	const [user] = await db
		.select({ logtoSub: users.logtoSub })
		.from(users)
		.where(eq(users.id, userId));
	if (!user) throw new Error("User not found");
	return user.logtoSub;
}

// ── Change Password (D-05) ──────────────────────────────────────────

app.post("/password", zValidator("json", changePasswordSchema), async (c) => {
	const db = c.get("db") as Db;
	const userId = c.get("userId")!;
	const { currentPassword, newPassword } = c.req.valid("json");

	const logtoSub = await getLogtoSub(db, userId);

	// Per T-28-03: ALWAYS verify current password before setting new one
	const isValid = await logtoClient.verifyPassword(logtoSub, currentPassword);
	if (!isValid) {
		return c.json({ error: "Current password is incorrect" }, 400);
	}

	await logtoClient.updatePassword(logtoSub, newPassword);
	return c.json({ ok: true });
});

// ── Change Email (D-05) ─────────────────────────────────────────────

app.post("/email", zValidator("json", changeEmailSchema), async (c) => {
	const db = c.get("db") as Db;
	const userId = c.get("userId")!;
	const { newEmail } = c.req.valid("json");

	const logtoSub = await getLogtoSub(db, userId);
	await logtoClient.updateEmail(logtoSub, newEmail);
	return c.json({ ok: true, email: newEmail });
});

// ── Has Password ────────────────────────────────────────────────────

app.get("/has-password", async (c) => {
	const db = c.get("db") as Db;
	const userId = c.get("userId")!;

	const logtoSub = await getLogtoSub(db, userId);
	const hasPassword = await logtoClient.hasPassword(logtoSub);
	return c.json({ hasPassword });
});

// ── Delete Account (D-05, D-06) ─────────────────────────────────────

app.post("/delete", zValidator("json", deleteAccountSchema), async (c) => {
	const db = c.get("db") as Db;
	const userId = c.get("userId")!;

	const logtoSub = await getLogtoSub(db, userId);

	// Per T-28-06: Run deletion in a single transaction
	await db.transaction(async (tx) => {
		// 1. Get or create sentinel "Deleted User" for public setup attribution (D-06)
		let [sentinel] = await tx
			.select({ id: users.id })
			.from(users)
			.where(eq(users.logtoSub, "deleted-user"));

		if (!sentinel) {
			[sentinel] = await tx
				.insert(users)
				.values({
					logtoSub: "deleted-user",
					displayName: "Deleted User",
				})
				.returning({ id: users.id });
		}

		// 2. Reassign public setups to sentinel user
		await tx
			.update(setups)
			.set({ userId: sentinel.id })
			.where(and(eq(setups.userId, userId), eq(setups.visibility, "public")));

		// 3. Get private setup IDs for cleanup
		const privateSetups = await tx
			.select({ id: setups.id })
			.from(setups)
			.where(eq(setups.userId, userId));

		if (privateSetups.length > 0) {
			const privateSetupIds = privateSetups.map((s) => s.id);
			// Delete setup items for private setups
			await tx
				.delete(setupItems)
				.where(inArray(setupItems.setupId, privateSetupIds));
			// Delete private setups
			await tx.delete(setups).where(eq(setups.userId, userId));
		}

		// 4. Get thread IDs for candidate cleanup
		const userThreads = await tx
			.select({ id: threads.id })
			.from(threads)
			.where(eq(threads.userId, userId));

		if (userThreads.length > 0) {
			const threadIds = userThreads.map((t) => t.id);
			// Delete thread candidates (cascade should handle this, but be explicit)
			await tx
				.delete(threadCandidates)
				.where(inArray(threadCandidates.threadId, threadIds));
		}

		// 5. Delete threads
		await tx.delete(threads).where(eq(threads.userId, userId));

		// 6. Delete items
		await tx.delete(items).where(eq(items.userId, userId));

		// 7. Delete categories
		await tx.delete(categories).where(eq(categories.userId, userId));

		// 8. Delete API keys
		await tx.delete(apiKeys).where(eq(apiKeys.userId, userId));

		// 9. Delete settings
		await tx.delete(settings).where(eq(settings.userId, userId));

		// 10. Delete OAuth codes and tokens
		await tx.delete(oauthCodes).where(eq(oauthCodes.userId, userId));
		await tx.delete(oauthTokens).where(eq(oauthTokens.userId, userId));

		// 11. Delete user record
		await tx.delete(users).where(eq(users.id, userId));
	});

	// Delete user from Logto (outside transaction — external service)
	try {
		await logtoClient.deleteUser(logtoSub);
	} catch (err) {
		console.error("[account] Failed to delete Logto user:", err);
		// Don't fail the request — local data is already cleaned up
	}

	return c.json({ ok: true, redirectTo: "/logout" });
});

export const accountRoutes = app;
