import { getAuth } from "@hono/oidc-auth";
import { Hono } from "hono";
import { db as prodDb } from "../../db/index.ts";
import {
	cleanExpiredOAuthData,
	createAuthorizationCode,
	exchangeCode,
	getClient,
	refreshAccessToken,
	registerClient,
} from "../services/oauth.service.ts";

type Env = { Variables: { db?: any } };

function escapeHtml(str: string): string {
	return str
		.replace(/&/g, "&amp;")
		.replace(/</g, "&lt;")
		.replace(/>/g, "&gt;")
		.replace(/"/g, "&quot;")
		.replace(/'/g, "&#x27;");
}

function getBaseUrl(c: any): string {
	if (process.env.GEARBOX_URL)
		return process.env.GEARBOX_URL.replace(/\/$/, "");
	return new URL(c.req.url).origin;
}

function renderConsentForm(params: {
	clientName: string;
	clientId: string;
	redirectUri: string;
	codeChallenge: string;
	codeChallengeMethod: string;
	state: string;
}): string {
	return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Authorize - GearBox</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f9fafb; display: flex; align-items: center; justify-content: center; min-height: 100vh; }
  .card { background: #fff; border-radius: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); padding: 32px; width: 100%; max-width: 400px; }
  h1 { font-size: 24px; font-weight: 700; text-align: center; margin-bottom: 8px; }
  .subtitle { text-align: center; color: #6b7280; margin-bottom: 24px; font-size: 14px; }
  button { width: 100%; padding: 10px; background: #2563eb; color: #fff; border: none; border-radius: 8px; font-size: 14px; font-weight: 600; cursor: pointer; }
  button:hover { background: #1d4ed8; }
</style>
</head>
<body>
<div class="card">
  <h1>GearBox</h1>
  <p class="subtitle">Authorize ${escapeHtml(params.clientName)} to access your data</p>
  <form method="POST" action="/oauth/authorize">
    <input type="hidden" name="client_id" value="${escapeHtml(params.clientId)}">
    <input type="hidden" name="redirect_uri" value="${escapeHtml(params.redirectUri)}">
    <input type="hidden" name="code_challenge" value="${escapeHtml(params.codeChallenge)}">
    <input type="hidden" name="code_challenge_method" value="${escapeHtml(params.codeChallengeMethod)}">
    <input type="hidden" name="state" value="${escapeHtml(params.state)}">
    <button type="submit">Authorize</button>
  </form>
</div>
</body>
</html>`;
}

// ── Well-Known Route ────────────────────────────────────────────────

export const wellKnownRoute = new Hono<Env>();

// Protected Resource Metadata (RFC 9728) — Claude fetches this first after 401
wellKnownRoute.get("/oauth-protected-resource", (c) => {
	const baseUrl = getBaseUrl(c);
	return c.json({
		resource: `${baseUrl}/mcp`,
		authorization_servers: [baseUrl],
	});
});

// OAuth Authorization Server Metadata (RFC 8414) — Claude fetches this second
wellKnownRoute.get("/oauth-authorization-server", (c) => {
	const baseUrl = getBaseUrl(c);
	return c.json({
		issuer: baseUrl,
		authorization_endpoint: `${baseUrl}/oauth/authorize`,
		token_endpoint: `${baseUrl}/oauth/token`,
		registration_endpoint: `${baseUrl}/oauth/register`,
		response_types_supported: ["code"],
		grant_types_supported: ["authorization_code", "refresh_token"],
		code_challenge_methods_supported: ["S256"],
		token_endpoint_auth_methods_supported: ["none"],
	});
});

// ── OAuth Routes ────────────────────────────────────────────────────

export const oauthRoutes = new Hono<Env>();

// POST /register — Dynamic Client Registration (RFC 7591)
oauthRoutes.post("/register", async (c) => {
	const db = c.get("db") ?? prodDb;
	const body = await c.req.json();

	if (
		!body.redirect_uris ||
		!Array.isArray(body.redirect_uris) ||
		body.redirect_uris.length === 0
	) {
		return c.json(
			{ error: "redirect_uris is required and must be a non-empty array" },
			400,
		);
	}

	const clientName = body.client_name || "Unknown Client";
	const { clientId } = await registerClient(db, clientName, body.redirect_uris);

	return c.json(
		{
			client_id: clientId,
			client_name: clientName,
			redirect_uris: body.redirect_uris,
		},
		201,
	);
});

// GET /authorize — Show consent form (requires OIDC session)
oauthRoutes.get("/authorize", async (c) => {
	const db = c.get("db") ?? prodDb;

	const auth = await getAuth(c);
	if (!auth) {
		return c.redirect(`/login?redirect=${encodeURIComponent(c.req.url)}`);
	}

	const responseType = c.req.query("response_type");
	const clientId = c.req.query("client_id");
	const redirectUri = c.req.query("redirect_uri");
	const codeChallenge = c.req.query("code_challenge");
	const codeChallengeMethod = c.req.query("code_challenge_method");
	const state = c.req.query("state") ?? "";

	if (responseType !== "code") {
		return c.json({ error: "response_type must be 'code'" }, 400);
	}
	if (!clientId || !redirectUri || !codeChallenge || !codeChallengeMethod) {
		return c.json({ error: "Missing required parameters" }, 400);
	}

	const client = await getClient(db, clientId);
	if (!client) {
		return c.json({ error: "Unknown client_id" }, 400);
	}

	const allowedUris: string[] = JSON.parse(client.redirectUris);
	if (!allowedUris.includes(redirectUri)) {
		return c.json({ error: "redirect_uri not allowed" }, 400);
	}

	return c.html(
		renderConsentForm({
			clientName: client.clientName,
			clientId,
			redirectUri,
			codeChallenge,
			codeChallengeMethod,
			state,
		}),
	);
});

// POST /authorize — Process consent (requires OIDC session)
oauthRoutes.post("/authorize", async (c) => {
	const db = c.get("db") ?? prodDb;

	// Check for OIDC session instead of username/password
	const auth = await getAuth(c);
	if (!auth) {
		const currentUrl = c.req.url;
		return c.redirect(`/login?redirect=${encodeURIComponent(currentUrl)}`);
	}

	const body = await c.req.parseBody();
	const clientId = body.client_id as string;
	const redirectUri = body.redirect_uri as string;
	const codeChallenge = body.code_challenge as string;
	const codeChallengeMethod = body.code_challenge_method as string;
	const state = (body.state as string) ?? "";

	const client = await getClient(db, clientId);
	if (!client) {
		return c.json({ error: "Unknown client_id" }, 400);
	}

	const allowedUris: string[] = JSON.parse(client.redirectUris);
	if (!allowedUris.includes(redirectUri)) {
		return c.json({ error: "redirect_uri not allowed" }, 400);
	}

	// Get or create user from OIDC session
	const { getOrCreateUser } = await import("../services/auth.service");
	const user = await getOrCreateUser(db, auth.sub);

	const { code } = await createAuthorizationCode(
		db,
		clientId,
		user.id,
		codeChallenge,
		codeChallengeMethod,
		redirectUri,
	);

	const url = new URL(redirectUri);
	url.searchParams.set("code", code);
	if (state) url.searchParams.set("state", state);

	return c.redirect(url.toString(), 302);
});

// POST /token — Token exchange
oauthRoutes.post("/token", async (c) => {
	const db = c.get("db") ?? prodDb;
	const body = await c.req.parseBody();

	const grantType = body.grant_type as string;

	// Opportunistic cleanup
	await cleanExpiredOAuthData(db);

	if (grantType === "authorization_code") {
		const code = body.code as string;
		const codeVerifier = body.code_verifier as string;
		const clientId = body.client_id as string;
		const redirectUri = body.redirect_uri as string;

		const result = await exchangeCode(
			db,
			code,
			codeVerifier,
			clientId,
			redirectUri,
		);
		if (!result) {
			return c.json({ error: "invalid_grant" }, 400);
		}

		return c.json({
			access_token: result.accessToken,
			refresh_token: result.refreshToken,
			token_type: "Bearer",
			expires_in: result.expiresIn,
		});
	}

	if (grantType === "refresh_token") {
		const refreshToken = body.refresh_token as string;
		const clientId = body.client_id as string;

		const result = await refreshAccessToken(db, refreshToken, clientId);
		if (!result) {
			return c.json({ error: "invalid_grant" }, 400);
		}

		return c.json({
			access_token: result.accessToken,
			refresh_token: result.refreshToken,
			token_type: "Bearer",
			expires_in: result.expiresIn,
		});
	}

	return c.json({ error: "unsupported_grant_type" }, 400);
});
