import type { FastifyInstance } from "fastify";
import { and, eq } from "drizzle-orm";
import { schema } from "@app/database";
import {
  createHouseholdInputSchema,
  createStorageLocationInputSchema,
  idParamSchema,
  joinHouseholdInputSchema,
  memberParamSchema,
  updateHouseholdInputSchema,
  updateMemberInputSchema,
  updateStorageLocationInputSchema,
} from "@app/validation";
import { loadEntitlements } from "../lib/entitlements.js";
import { errors, parse } from "../lib/errors.js";
import { audit, emitEvent, generateInviteCode, requireMembership } from "../lib/helpers.js";

/** Hushåll (spec §7): delat lager/plan/lista, individuella mål och roller. */
export async function householdRoutes(app: FastifyInstance) {
  const auth = { preHandler: [app.authenticate] };

  app.get("/v1/households", auth, async (req) => {
    const rows = await app.db
      .select({
        household: schema.households,
        role: schema.householdMembers.role,
        portionFactor: schema.householdMembers.portionFactor,
      })
      .from(schema.householdMembers)
      .innerJoin(schema.households, eq(schema.householdMembers.householdId, schema.households.id))
      .where(eq(schema.householdMembers.userId, req.userId));
    return rows.map((r) => ({ ...r.household, myRole: r.role, myPortionFactor: r.portionFactor }));
  });

  app.post("/v1/households", auth, async (req, reply) => {
    const input = parse(createHouseholdInputSchema, req.body);
    const [household] = await app.db
      .insert(schema.households)
      .values({
        name: input.name,
        inviteCode: generateInviteCode(),
        weeklyBudgetMinor: input.weeklyBudgetMinor ?? null,
        ...(input.currencyCode ? { currencyCode: input.currencyCode } : {}),
      })
      .returning();
    await app.db
      .insert(schema.householdMembers)
      .values({ householdId: household!.id, userId: req.userId, role: "owner" });
    await app.db.insert(schema.storageLocations).values([
      { householdId: household!.id, type: "fridge", name: "Kylen", sortOrder: 0 },
      { householdId: household!.id, type: "freezer", name: "Frysen", sortOrder: 1 },
      { householdId: household!.id, type: "pantry", name: "Skafferiet", sortOrder: 2 },
    ]);
    return reply.status(201).send(household);
  });

  app.get("/v1/households/:id", auth, async (req) => {
    const { id } = parse(idParamSchema, req.params);
    await requireMembership(app.db, id, req.userId);
    const [household] = await app.db
      .select()
      .from(schema.households)
      .where(eq(schema.households.id, id))
      .limit(1);
    if (!household) throw errors.notFound();

    const members = await app.db
      .select({
        userId: schema.householdMembers.userId,
        role: schema.householdMembers.role,
        portionFactor: schema.householdMembers.portionFactor,
        joinedAt: schema.householdMembers.joinedAt,
        displayName: schema.users.displayName,
      })
      .from(schema.householdMembers)
      .innerJoin(schema.users, eq(schema.householdMembers.userId, schema.users.id))
      .where(eq(schema.householdMembers.householdId, id));

    const locations = await app.db
      .select()
      .from(schema.storageLocations)
      .where(eq(schema.storageLocations.householdId, id))
      .orderBy(schema.storageLocations.sortOrder);

    // OBS: individuella hälsomål/allergier exponeras INTE här (spec §7, §56).
    return { ...household, members, storageLocations: locations };
  });

  app.patch("/v1/households/:id", auth, async (req) => {
    const { id } = parse(idParamSchema, req.params);
    const membership = await requireMembership(app.db, id, req.userId);
    if (membership.role !== "owner" && membership.role !== "adult") {
      throw errors.forbidden("Endast vuxna medlemmar kan ändra hushållet.");
    }
    const input = parse(updateHouseholdInputSchema, req.body);
    const [row] = await app.db
      .update(schema.households)
      .set({ ...input, updatedAt: new Date() })
      .where(eq(schema.households.id, id))
      .returning();
    return row;
  });

  app.post("/v1/households/join", auth, async (req) => {
    const input = parse(joinHouseholdInputSchema, req.body);
    const [household] = await app.db
      .select()
      .from(schema.households)
      .where(eq(schema.households.inviteCode, input.inviteCode.toUpperCase()))
      .limit(1);
    if (!household) throw errors.notFound("Ingen hushållsinbjudan matchar koden.");

    // Kontrollera plangräns: max medlemmar styrs av ägarens plan (spec §45).
    const [owner] = await app.db
      .select({ userId: schema.householdMembers.userId })
      .from(schema.householdMembers)
      .where(
        and(
          eq(schema.householdMembers.householdId, household.id),
          eq(schema.householdMembers.role, "owner"),
        ),
      )
      .limit(1);
    const members = await app.db
      .select({ userId: schema.householdMembers.userId })
      .from(schema.householdMembers)
      .where(eq(schema.householdMembers.householdId, household.id));
    if (owner) {
      const ent = await loadEntitlements(app.db, owner.userId);
      if (members.length >= ent.maxHouseholdMembers) {
        throw errors.paymentRequired(
          `Hushållet har nått maxantalet medlemmar (${ent.maxHouseholdMembers}) för sin plan.`,
        );
      }
    }

    await app.db
      .insert(schema.householdMembers)
      .values({ householdId: household.id, userId: req.userId, role: "adult" })
      .onConflictDoNothing();

    await emitEvent(app.db, {
      type: "HOUSEHOLD_MEMBER_ADDED",
      payload: { householdId: household.id, newUserId: req.userId, role: "adult" },
      userId: req.userId,
      householdId: household.id,
      correlationId: req.correlationId,
    });
    return { ok: true, household: { id: household.id, name: household.name } };
  });

  app.patch("/v1/households/:id/members/:userId", auth, async (req) => {
    const { id, userId } = parse(memberParamSchema, req.params);
    const membership = await requireMembership(app.db, id, req.userId);
    const isSelf = userId === req.userId;
    if (!isSelf && membership.role !== "owner" && membership.role !== "adult") {
      throw errors.forbidden("Endast vuxna kan ändra andra medlemmar.");
    }
    const input = parse(updateMemberInputSchema, req.body);
    if (input.role && membership.role !== "owner") {
      throw errors.forbidden("Endast ägaren kan ändra roller.");
    }
    const [row] = await app.db
      .update(schema.householdMembers)
      .set(input)
      .where(
        and(
          eq(schema.householdMembers.householdId, id),
          eq(schema.householdMembers.userId, userId),
        ),
      )
      .returning();
    if (!row) throw errors.notFound("Medlemmen finns inte.");
    return row;
  });

  app.delete("/v1/households/:id/members/:userId", auth, async (req) => {
    const { id, userId } = parse(memberParamSchema, req.params);
    const membership = await requireMembership(app.db, id, req.userId);
    const isSelf = userId === req.userId;
    if (!isSelf && membership.role !== "owner") {
      throw errors.forbidden("Endast ägaren kan ta bort andra medlemmar.");
    }
    await app.db
      .delete(schema.householdMembers)
      .where(
        and(
          eq(schema.householdMembers.householdId, id),
          eq(schema.householdMembers.userId, userId),
        ),
      );
    await audit(app.db, {
      actorUserId: req.userId,
      action: "household.member_removed",
      targetType: "user",
      targetId: userId,
    });
    return { ok: true };
  });

  // --- Förvaringsplatser (spec §8) ---
  app.post("/v1/households/:id/storage-locations", auth, async (req, reply) => {
    const { id } = parse(idParamSchema, req.params);
    await requireMembership(app.db, id, req.userId);
    const input = parse(createStorageLocationInputSchema, req.body);
    const [row] = await app.db
      .insert(schema.storageLocations)
      .values({ householdId: id, ...input })
      .returning();
    return reply.status(201).send(row);
  });

  app.patch("/v1/households/:id/storage-locations/:locationId", auth, async (req) => {
    const params = req.params as { id: string; locationId: string };
    await requireMembership(app.db, params.id, req.userId);
    const input = parse(updateStorageLocationInputSchema, req.body);
    const [row] = await app.db
      .update(schema.storageLocations)
      .set(input)
      .where(
        and(
          eq(schema.storageLocations.id, params.locationId),
          eq(schema.storageLocations.householdId, params.id),
        ),
      )
      .returning();
    if (!row) throw errors.notFound();
    return row;
  });
}
