import { Hono } from "hono";
import { desc, eq } from "drizzle-orm";
import dayjs from "dayjs";
import { useDrizzle } from "@/lib/db.js";
import { idTag } from "@/db/schema.js";
import { z } from "zod";
import type { HonoEnv } from "@/types/hono.ts";

const app = new Hono<HonoEnv>();

const idTagSchema = z.object({
  idTag: z.string().min(1).max(20),
  parentIdTag: z.string().max(20).optional().nullable(),
  status: z.enum(["Accepted", "Blocked", "Expired", "Invalid", "ConcurrentTx"]).default("Accepted"),
  expiryDate: z.string().date().optional().nullable(),
  userId: z.string().optional().nullable(),
  balance: z.number().int().min(0).default(0),
  cardLayout: z.enum(["center", "around"]).optional(),
  cardSkin: z.enum(["line", "circles", "glow", "vip", "redeye"]).optional(),
});

const idTagUpdateSchema = idTagSchema.partial().omit({ idTag: true });

/** Generate an 8-character OCPP-compatible idTag (0-9 + A-F uppercase) */
function generateIdTag(): string {
  const chars = "0123456789ABCDEF";
  let result = "";
  for (let i = 0; i < 8; i++) {
    result += chars[Math.floor(Math.random() * chars.length)];
  }
  return result;
}

/** GET /api/id-tags */
app.get("/", async (c) => {
  const db = useDrizzle();
  const currentUser = c.get("user");
  if (!currentUser) return c.json({ error: "Unauthorized" }, 401);

  // Non-admin users only see their own cards
  if (currentUser.role !== "admin") {
    const tags = await db
      .select()
      .from(idTag)
      .where(eq(idTag.userId, currentUser.id))
      .orderBy(desc(idTag.createdAt));
    return c.json(tags);
  }

  const tags = await db.select().from(idTag).orderBy(desc(idTag.createdAt));
  return c.json(tags);
});

/** GET /api/id-tags/:id */
app.get("/:id", async (c) => {
  const db = useDrizzle();
  const tagId = c.req.param("id");
  const [tag] = await db.select().from(idTag).where(eq(idTag.idTag, tagId)).limit(1);
  if (!tag) return c.json({ error: "Not found" }, 404);
  return c.json(tag);
});

/** POST /api/id-tags  —  admin only, create arbitrary card */
app.post("/", async (c) => {
  if (c.get("user")?.role !== "admin") return c.json({ error: "Forbidden" }, 403);
  const db = useDrizzle();
  const body = await c.req.json().catch(() => {});
  const parsed = idTagSchema.safeParse(body);
  if (!parsed.success) return c.json({ error: parsed.error.issues }, 400);
  const [created] = await db
    .insert(idTag)
    .values({
      ...parsed.data,
      expiryDate: parsed.data.expiryDate ? dayjs(parsed.data.expiryDate).toDate() : null,
    })
    .returning();
  return c.json(created, 201);
});

/** POST /api/id-tags/claim  —  user claims a new card assigned to themselves */
app.post("/claim", async (c) => {
  const currentUser = c.get("user");
  if (!currentUser) return c.json({ error: "Unauthorized" }, 401);

  const db = useDrizzle();

  // Retry up to 5 times in case of collision
  for (let attempt = 0; attempt < 5; attempt++) {
    const newTag = generateIdTag();
    const existing = await db
      .select({ idTag: idTag.idTag })
      .from(idTag)
      .where(eq(idTag.idTag, newTag))
      .limit(1);
    if (existing.length > 0) continue;

    const [created] = await db
      .insert(idTag)
      .values({
        idTag: newTag,
        status: "Accepted",
        userId: currentUser.id,
        balance: 0,
      })
      .returning();
    return c.json(created, 201);
  }

  return c.json({ error: "Failed to generate unique idTag, please retry" }, 500);
});

/** PATCH /api/id-tags/:id  —  admin only */
app.patch("/:id", async (c) => {
  if (c.get("user")?.role !== "admin") return c.json({ error: "Forbidden" }, 403);
  const db = useDrizzle();
  const tagId = c.req.param("id");
  const body = await c.req.json().catch(() => {});
  const parsed = idTagUpdateSchema.safeParse(body);
  if (!parsed.success) return c.json({ error: parsed.error.issues }, 400);

  const [updated] = await db
    .update(idTag)
    .set({
      ...parsed.data,
      expiryDate: parsed.data.expiryDate ? dayjs(parsed.data.expiryDate).toDate() : undefined,
      updatedAt: dayjs().toDate(),
    })
    .where(eq(idTag.idTag, tagId))
    .returning();

  if (!updated) return c.json({ error: "Not found" }, 404);
  return c.json(updated);
});

/** DELETE /api/id-tags/:id  —  admin only */
app.delete("/:id", async (c) => {
  if (c.get("user")?.role !== "admin") return c.json({ error: "Forbidden" }, 403);
  const db = useDrizzle();
  const tagId = c.req.param("id");

  const [deleted] = await db
    .delete(idTag)
    .where(eq(idTag.idTag, tagId))
    .returning({ idTag: idTag.idTag });

  if (!deleted) return c.json({ error: "Not found" }, 404);
  return c.json({ success: true });
});

export default app;
