import { db, eq, and } from "@haushaltsApp/db";
import { monthStatus, monthlyTransfers } from "@haushaltsApp/db/schema";

export type MonthStatus = {
  id: string;
  householdId: string;
  month: string;
  status: "open" | "closed";
  closedAt: Date | null;
  closedBy: string | null;
  finalAmount: number | null;
  notes: string | null;
  finalTransferId: string | null;
  createdAt: Date;
};

function mapRow(row: typeof monthStatus.$inferSelect): MonthStatus {
  return {
    id: row.id,
    householdId: row.householdId,
    month: row.month,
    status: row.status as "open" | "closed",
    closedAt: row.closedAt,
    closedBy: row.closedBy,
    finalAmount: row.finalAmount !== null ? parseFloat(row.finalAmount) : null,
    notes: row.notes,
    finalTransferId: row.finalTransferId,
    createdAt: row.createdAt,
  };
}

export async function getMonthStatus(householdId: string, month: string): Promise<MonthStatus> {
  const existing = await db
    .select()
    .from(monthStatus)
    .where(and(eq(monthStatus.householdId, householdId), eq(monthStatus.month, month)))
    .limit(1);

  if (existing[0]) return mapRow(existing[0]);

  // Return a virtual "open" status — no DB row needed for open months
  return {
    id: "",
    householdId,
    month,
    status: "open",
    closedAt: null,
    closedBy: null,
    finalAmount: null,
    notes: null,
    finalTransferId: null,
    createdAt: new Date(),
  };
}

export type CloseMonthInput = {
  finalAmount: number;
  notes?: string;
  toUserId: string; // who receives the transfer
};

export async function closeMonth(
  householdId: string,
  month: string,
  userId: string,
  input: CloseMonthInput,
): Promise<MonthStatus> {
  // Guard: only current or past months
  const now = new Date();
  const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
  if (month > currentMonth) {
    throw new Error("Zukünftige Monate können nicht abgeschlossen werden.");
  }

  // Guard: must be open
  const current = await getMonthStatus(householdId, month);
  if (current.status === "closed") {
    throw new Error("Dieser Monat ist bereits abgeschlossen.");
  }

  // Book the final transfer if amount > 0
  let finalTransferId: string | null = null;
  if (input.finalAmount > 0) {
    const [transfer] = await db
      .insert(monthlyTransfers)
      .values({
        householdId,
        month,
        fromUserId: userId,
        toUserId: input.toUserId,
        amount: String(input.finalAmount),
        note: input.notes ?? `Monatsabschluss ${month}`,
      })
      .returning({ id: monthlyTransfers.id });
    finalTransferId = transfer?.id ?? null;
  }

  // Upsert month_status row (unique on householdId+month)
  const closeValues = {
    status: "closed" as const,
    closedAt: new Date(),
    closedBy: userId,
    finalAmount: String(input.finalAmount),
    notes: input.notes ?? null,
    finalTransferId,
  };
  const [row] = await db
    .insert(monthStatus)
    .values({ householdId, month, ...closeValues })
    .onConflictDoUpdate({
      target: [monthStatus.householdId, monthStatus.month],
      set: closeValues,
    })
    .returning();

  return mapRow(row!);
}

export async function reopenMonth(
  householdId: string,
  month: string,
): Promise<MonthStatus> {
  // Find existing row
  const existing = await db
    .select()
    .from(monthStatus)
    .where(and(eq(monthStatus.householdId, householdId), eq(monthStatus.month, month)))
    .limit(1);

  if (!existing[0] || existing[0].status === "open") {
    return getMonthStatus(householdId, month);
  }

  // Delete the final transfer that was booked on close
  if (existing[0].finalTransferId) {
    await db
      .delete(monthlyTransfers)
      .where(eq(monthlyTransfers.id, existing[0].finalTransferId));
  }

  const [row] = await db
    .update(monthStatus)
    .set({
      status: "open",
      closedAt: null,
      closedBy: null,
      finalAmount: null,
      notes: null,
      finalTransferId: null,
    })
    .where(and(eq(monthStatus.householdId, householdId), eq(monthStatus.month, month)))
    .returning();

  return mapRow(row!);
}
