import { OAuthResolverError } from "@atproto/oauth-client-node";
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { generateSignedCookie, getSignedCookie } from "hono/cookie";
import { client } from "./atproto/client";
import { resolveMiniDoc } from "./utils";
import "dotenv/config";
import { jetstream } from "./ingester";
import { Agent } from "@atproto/api";
import { TID } from "@atproto/common";
import { AppFooodzReview } from "./lexicons";
import { is } from "@atcute/lexicons";
import { Layout } from "./components";
import { db } from "./drizzle/db";
import { users } from "./drizzle/schema";
import { eq } from "drizzle-orm";

const app = new Hono();

app.get("/client-metadata.json", (c) => {
	return c.json(client.clientMetadata);
});

app.get("/oauth/callback", async (c) => {
	const params = new URLSearchParams(c.req.url.split("?")[1]);

	try {
		const { session } = await client.callback(params);

		// check if user is already added to db
		const user = await db.query.users.findFirst({
			where: eq(users.did, session.did),
		});

		if (!user) {
			console.log("user not found in db, inserting");
			await db.insert(users).values({
				did: session.did,
			});
		}

		const cookie = await generateSignedCookie(
			"__foooodz_session",
			session.did,
			process.env.COOKIE_SECRET as string,
			{
				path: "/",
				httpOnly: true,
				sameSite: "lax",
				maxAge: 60 * 60 * 24 * 7,
				secure: process.env.NODE_ENV === "production",
			},
		);
		return new Response(null, {
			status: 302,
			headers: {
				"Set-Cookie": cookie,
				Location: "/",
			},
		});
	} catch (error) {
		if (error instanceof Error) {
			throw error;
		}
	}
});

app.post("/login", async (c) => {
	try {
		const formData = await c.req.formData();
		const handle = formData.get("handle");
		if (!handle || typeof handle !== "string") {
			return c.json(
				{
					ok: false,
					message: "Invalid handle",
				},
				400,
			);
		}

		const { did } = await resolveMiniDoc(handle);

		const ac = new AbortController();
		const redirectUrl = await client.authorize(did, {
			signal: ac.signal,
		});
		return c.redirect(redirectUrl.toString());
	} catch (error) {
		if (error instanceof OAuthResolverError) {
			return c.json({ ok: false, message: error.message }, 500);
		}
		console.log(error);
		return c.json({ ok: false, message: "something went wrong" }, 500);
	}
});

app.get("/", async (c) => {
	return c.html(
		<Layout>
			<form method="post" action="/login">
				<label htmlFor="handle">Handle</label>
				<input type="text" name="handle" id="handle" />
				<button type="submit">Login</button>
			</form>
		</Layout>,
	);
});

app.get("/review", async (c) => {
	const reviews = await db.query.reviews.findMany({
		limit: 10,
	});
	return c.html(
		<Layout>
			<form method="post" action="/review">
				<label htmlFor="place">place</label>
				<input type="text" name="place" id="place" maxlength={200} required />
				<label htmlFor="review">review</label>
				<textarea name="review" id="review" maxlength={3000} required />
				<label htmlFor="rating">rating</label>
				<select name="rating" id="rating" required>
					{Array.from({ length: 5 }, (_, index) => (
						<option key={index} value={index + 1}>
							{index + 1}
						</option>
					))}
				</select>
				<button type="submit">submit review</button>
			</form>
			<ul>
				{reviews.map((review) => (
					<li key={review.rkey}>
						<article>
							<h2>{review.place}</h2>
							<p>{review.review}</p>
							<span>{review.rating}</span>
							<time datetime={review.createdAt}>{review.createdAt}</time>
						</article>
					</li>
				))}
			</ul>
		</Layout>,
	);
});

app.post("/review", async (c) => {
	const cookie = await getSignedCookie(c, process.env.COOKIE_SECRET as string);
	if (cookie.__foooodz_session) {
		try {
			const oauthSession = await client.restore(cookie.__foooodz_session);
			const agent = new Agent(oauthSession);
			const formData = await c.req.formData();
			const place = formData.get("place") as string;
			const review = formData.get("review") as string;
			const rating = formData.get("rating") as string;

			const rkey = TID.nextStr();

			const record: AppFooodzReview.Main = {
				$type: "app.fooodz.review",
				place,
				rating: +rating,
				review,
				createdAt: new Date().toISOString(),
			};

			if (!is(AppFooodzReview.mainSchema, record)) {
				return c.json({ ok: false, message: "invalid review record" }, 400);
			}

			await agent.com.atproto.repo.putRecord({
				repo: agent.assertDid,
				rkey,
				record,
				collection: "app.fooodz.review",
				validate: false,
			});

			return c.redirect("/review");
		} catch (error) {
			return c.json({ ok: false, message: "failed to write record" }, 500);
		}
	}
});

serve(
	{
		fetch: app.fetch,
		port: 3000,
	},
	(info) => {
		console.log(`Server is running @ http://localhost:${info.port}`);
		jetstream.start();
	},
);
