import {
	oidcAuthMiddleware,
	processOAuthCallback,
	revokeSession,
} from "@hono/oidc-auth";
import { Hono } from "hono";
import { serveStatic } from "hono/bun";
import { cors } from "hono/cors";
import { db as prodDb } from "../db/index.ts";
import { seedDefaults } from "../db/seed.ts";
import { mcpRoutes } from "./mcp/index.ts";
import { requireAuth } from "./middleware/auth.ts";
import { createRateLimit } from "./middleware/rateLimit.ts";
import { accountRoutes } from "./routes/account.ts";
import { authRoutes } from "./routes/auth.ts";
import { categoryRoutes } from "./routes/categories.ts";
import { discoveryRoutes } from "./routes/discovery.ts";
import { globalItemRoutes } from "./routes/global-items.ts";
import { imageRoutes } from "./routes/images.ts";
import { itemRoutes } from "./routes/items.ts";
import { oauthRoutes, wellKnownRoute } from "./routes/oauth.ts";
import { onboardingRoutes } from "./routes/onboarding.ts";
import { profileRoutes } from "./routes/profiles.ts";
import { settingsRoutes } from "./routes/settings.ts";
import { setupRoutes } from "./routes/setups.ts";
import { tagRoutes } from "./routes/tags.ts";
import { threadRoutes } from "./routes/threads.ts";
import { totalRoutes } from "./routes/totals.ts";

// Seed default data on startup
await seedDefaults();

// OIDC connectivity pre-check: verify Logto discovery is reachable at startup
// This surfaces network/config errors early in logs rather than hiding them as "Invalid session"
const oidcIssuer = process.env.OIDC_ISSUER;
if (oidcIssuer) {
	const discoveryUrl = `${oidcIssuer}/.well-known/openid-configuration`;
	fetch(discoveryUrl)
		.then(async (res) => {
			if (!res.ok) {
				console.error(
					`[OIDC] Discovery endpoint returned HTTP ${res.status}: ${discoveryUrl}`,
				);
			} else {
				console.log(`[OIDC] Discovery endpoint reachable: ${discoveryUrl}`);
			}
		})
		.catch((err) => {
			console.error(
				`[OIDC] Discovery endpoint unreachable: ${discoveryUrl}`,
				err,
			);
			console.error(
				"[OIDC] This will cause 'Invalid session' errors on /login — check network connectivity to Logto",
			);
		});
} else {
	console.warn("[OIDC] OIDC_ISSUER is not set — OIDC authentication will fail");
}

const app = new Hono();

// Centralized error handler
app.onError((err, c) => {
	console.error(`[${c.req.method}] ${c.req.path}:`, err);
	// HTTPException has a getResponse() method — use it to preserve the original status/message
	if ("getResponse" in err && typeof (err as any).getResponse === "function") {
		return (err as any).getResponse();
	}
	const message =
		process.env.NODE_ENV === "production"
			? "Internal server error"
			: err.message || "Internal server error";
	return c.json({ error: message }, 500);
});

// Health check
app.get("/api/health", (c) => {
	return c.json({ status: "ok" });
});

// ── OIDC Browser Auth (top-level, before /api/* middleware) ───────────

// In dev mode, strip Secure flag from OIDC cookies so they work over HTTP
if (process.env.NODE_ENV !== "production") {
	app.use("*", async (c, next) => {
		await next();
		const setCookies = c.res.headers.getSetCookie?.() ?? [];
		if (setCookies.length > 0) {
			c.res.headers.delete("Set-Cookie");
			for (const cookie of setCookies) {
				c.res.headers.append("Set-Cookie", cookie.replace(/;\s*Secure/gi, ""));
			}
		}
	});
}

app.get("/login", oidcAuthMiddleware(), async (c) => c.redirect("/"));
app.get("/callback", async (c) => processOAuthCallback(c));
app.get("/logout", async (c) => {
	await revokeSession(c);
	const issuer = process.env.OIDC_ISSUER;
	const postLogoutRedirect = new URL("/", c.req.url).origin;
	if (issuer) {
		const clientId = process.env.OIDC_CLIENT_ID;
		const redirectUri = process.env.GEARBOX_URL || postLogoutRedirect;
		return c.redirect(
			`${issuer}/session/end?client_id=${encodeURIComponent(clientId || "")}&post_logout_redirect_uri=${encodeURIComponent(redirectUri)}`,
		);
	}
	return c.redirect("/");
});

// CORS for OAuth and MCP endpoints (required for claude.ai browser-based flows)
app.use("/.well-known/*", cors());
app.use("/oauth/*", cors());
app.use("/mcp/*", cors());

// OAuth routes (must be before /api/* middleware)
app.use("/oauth/*", async (c, next) => {
	c.set("db", prodDb);
	return next();
});
app.route("/.well-known", wellKnownRoute);
app.route("/oauth", oauthRoutes);

// Inject production database into request context
app.use("/api/*", async (c, next) => {
	c.set("db", prodDb);
	return next();
});

// Rate limiting for public endpoints (per D-07, D-08)
const browseTier = createRateLimit(120, 60_000);
const detailTier = createRateLimit(60, 60_000);

// Browse endpoints — higher limit for list/search
app.use("/api/discovery/*", async (c, next) => {
	if (c.req.method === "GET") return browseTier(c, next);
	return next();
});
app.use("/api/global-items", async (c, next) => {
	if (c.req.method === "GET" && !c.req.path.match(/^\/api\/global-items\/\d+$/))
		return browseTier(c, next);
	return next();
});
app.use("/api/tags", async (c, next) => {
	if (c.req.method === "GET") return browseTier(c, next);
	return next();
});

// Detail endpoints — moderate limit for individual resources
app.use("/api/global-items/:id", async (c, next) => {
	if (c.req.method === "GET") return detailTier(c, next);
	return next();
});
app.use("/api/setups/:id/public", async (c, next) => {
	if (c.req.method === "GET") return detailTier(c, next);
	return next();
});
app.use("/api/users/:id/profile", async (c, next) => {
	if (c.req.method === "GET") return detailTier(c, next);
	return next();
});

// Auth middleware for all data routes (userId must be available for per-user scoping)
app.use("/api/*", async (c, next) => {
	// Skip auth routes — they handle their own auth
	if (c.req.path.startsWith("/api/auth")) return next();
	// Skip health check
	if (c.req.path === "/api/health") return next();
	// Skip public profile endpoint (GET /api/users/:id/profile)
	if (/^\/api\/users\/\d+\/profile$/.test(c.req.path) && c.req.method === "GET")
		return next();
	// Skip public setup view (GET /api/setups/:id/public)
	if (/^\/api\/setups\/\d+\/public$/.test(c.req.path) && c.req.method === "GET")
		return next();
	// Skip public tags endpoint (GET /api/tags)
	if (c.req.path.startsWith("/api/tags") && c.req.method === "GET")
		return next();
	// Skip public discovery endpoints (GET /api/discovery/*)
	if (c.req.path.startsWith("/api/discovery") && c.req.method === "GET")
		return next();
	// Skip public global-items endpoint (GET /api/global-items)
	if (c.req.path.startsWith("/api/global-items") && c.req.method === "GET")
		return next();
	// All other methods require auth for userId resolution
	return requireAuth(c, next);
});

// API routes
app.route("/api/account", accountRoutes);
app.route("/api/auth", authRoutes);
app.route("/api/items", itemRoutes);
app.route("/api/categories", categoryRoutes);
app.route("/api/totals", totalRoutes);
app.route("/api/images", imageRoutes);
app.route("/api/settings", settingsRoutes);
app.route("/api/threads", threadRoutes);
app.route("/api/users", profileRoutes);
app.route("/api/setups", setupRoutes);
app.route("/api/discovery", discoveryRoutes);
app.route("/api/global-items", globalItemRoutes);
app.route("/api/onboarding", onboardingRoutes);
app.route("/api/tags", tagRoutes);

// MCP server (conditionally mounted)
if (process.env.GEARBOX_MCP !== "false") {
	app.route("/mcp", mcpRoutes);
}

// Serve Vite-built SPA in production
if (process.env.NODE_ENV === "production") {
	app.use("/*", serveStatic({ root: "./dist/client" }));
	app.get("*", serveStatic({ path: "./dist/client/index.html" }));
}

export default { port: 3000, fetch: app.fetch };
export { app };
