import { db, eq, and, isNull } from "@haushaltsApp/db";
import { householdInvitations, households, member } from "@haushaltsApp/db/schema";
import { joinWithCodeSchema } from "@haushaltsApp/shared/schemas/invite.schema";
import { Hono } from "hono";
import { authMiddleware, requireAuth, type AuthVariables } from "../middleware/auth.middleware";
import { tenantMiddleware, requireHousehold, type TenantVariables } from "../middleware/tenant.middleware";

type Variables = AuthVariables & TenantVariables;

const CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
const CODE_LENGTH = 6;
const EXPIRES_IN_MS = 24 * 60 * 60 * 1000; // 24 hours

function generateCode(): string {
  const bytes = crypto.getRandomValues(new Uint8Array(CODE_LENGTH));
  let code = "";
  for (const byte of bytes) {
    code += CODE_ALPHABET[byte % CODE_ALPHABET.length];
  }
  return code;
}

export const inviteRoutes = new Hono<{ Variables: Variables }>();

inviteRoutes.use("/*", authMiddleware, requireAuth);

// POST /api/households/invite/generate — create a new invite code for current household
inviteRoutes.post("/generate", tenantMiddleware, requireHousehold, async (c) => {
  const householdId = c.get("householdId") as string;
  const currentUser = c.get("user") as { id: string };
  const now = new Date();

  // Invalidate any existing active (non-expired, non-used) codes for this household
  const existingActive = await db
    .select({ id: householdInvitations.id })
    .from(householdInvitations)
    .where(
      and(
        eq(householdInvitations.householdId, householdId),
        isNull(householdInvitations.usedAt),
      ),
    );

  for (const row of existingActive) {
    // Mark as expired by setting expiresAt to now (effectively invalidating)
    await db
      .update(householdInvitations)
      .set({ expiresAt: now.toISOString() })
      .where(eq(householdInvitations.id, row.id));
  }

  const code = generateCode();
  const expiresAt = new Date(now.getTime() + EXPIRES_IN_MS).toISOString();

  await db.insert(householdInvitations).values({
    id: crypto.randomUUID(),
    householdId,
    code,
    createdBy: currentUser.id,
    expiresAt,
    createdAt: now.toISOString(),
  });

  return c.json({ code, expiresAt });
});

// POST /api/households/invite/join — join a household using an invite code
inviteRoutes.post("/join", async (c) => {
  const currentUser = c.get("user") as { id: string };

  const body = await c.req.json();
  const parsed = joinWithCodeSchema.safeParse(body);
  if (!parsed.success) {
    return c.json({ error: "Invalid request", issues: parsed.error.issues }, 400);
  }

  const code = parsed.data.code.toUpperCase();
  const now = new Date();

  // Look up the invitation by code
  const invite = await db
    .select()
    .from(householdInvitations)
    .where(eq(householdInvitations.code, code))
    .limit(1)
    .then((rows) => rows[0] ?? null);

  if (!invite) {
    return c.json({ error: "Invalid code" }, 404);
  }

  if (invite.usedAt !== null) {
    return c.json({ error: "Code already used" }, 409);
  }

  if (new Date(invite.expiresAt) < now) {
    return c.json({ error: "Code expired" }, 410);
  }

  // Check if user is already a member of that household
  const existingMembership = await db
    .select({ id: member.id })
    .from(member)
    .where(
      and(
        eq(member.organizationId, invite.householdId),
        eq(member.userId, currentUser.id),
      ),
    )
    .limit(1)
    .then((rows) => rows[0] ?? null);

  if (existingMembership) {
    return c.json({ error: "Already a member" }, 409);
  }

  // Insert into member table
  await db.insert(member).values({
    id: crypto.randomUUID(),
    organizationId: invite.householdId,
    userId: currentUser.id,
    role: "member",
    createdAt: now,
  });

  // Mark invitation as used
  await db
    .update(householdInvitations)
    .set({
      usedAt: now.toISOString(),
      usedBy: currentUser.id,
    })
    .where(eq(householdInvitations.id, invite.id));

  // Get household name
  const household = await db
    .select({ id: households.id, name: households.name })
    .from(households)
    .where(eq(households.id, invite.householdId))
    .limit(1)
    .then((rows) => rows[0] ?? null);

  return c.json({
    householdId: invite.householdId,
    householdName: household?.name ?? null,
  });
});
