import { db, eq, and, desc } from "@haushaltsApp/db";
import { fixedCosts, monthlyTransfers, transferLineItems, transactions } from "@haushaltsApp/db/schema";
import { member, user } from "@haushaltsApp/db/schema";
import { sql } from "@haushaltsApp/db";
import { getOrCreateHouseholdSettings } from "./household-settings.service";
import type {
  CreateFixedCostInput,
  UpdateFixedCostInput,
  CreateTransferLineItemInput,
  UpdateTransferLineItemInput,
  CreateMonthlyTransferInput,
} from "@haushaltsApp/shared/schemas/fixed-costs.schema";

// ── Types ─────────────────────────────────────────────────────────────────────

export type FixedCost = {
  id: string;
  householdId: string;
  scope: "household" | "private" | "child";
  childId: string | null;
  categoryId: string | null;
  label: string;
  amount: number;
  type: "income" | "expense";
  isActive: boolean;
  createdAt: Date;
};

export type TransferLineItem = {
  id: string;
  householdId: string;
  label: string;
  amount: number;
  isActive: boolean;
  createdAt: Date;
};

export type MonthlyTransfer = {
  id: string;
  householdId: string;
  month: string;
  fromUserId: string;
  toUserId: string;
  amount: number;
  note: string | null;
  createdAt: Date;
};

export type NettoMonth = {
  month: string;
  totalIncome: number;
  incomeByScope: Array<{ scope: string; label: string; amount: number }>;
  totalExpenses: number;
  netto: number;
};

export type SettlementV2 = {
  month: string;
  householdExpenses: number;
  householdIncome: number;
  householdNet: number;
  memberCount: number;
  perMemberShare: number;
  userSharePercent: number;
  payerUserId: string;
  isPayer: boolean;
  lineItems: Array<{ id: string; label: string; amount: number }>;
  lineItemsTotal: number;
  myOwnExpenses: number;
  transfers: MonthlyTransfer[];
  alreadyTransferred: number;
  totalOwed: number;
  remaining: number;
  members: Array<{ userId: string; name: string; paid: number; owes: number }>;
};

// ── Fixed Costs ───────────────────────────────────────────────────────────────

export async function getFixedCosts(householdId: string): Promise<FixedCost[]> {
  const rows = await db
    .select()
    .from(fixedCosts)
    .where(eq(fixedCosts.householdId, householdId))
    .orderBy(desc(fixedCosts.createdAt));

  return rows.map((r) => ({
    id: r.id,
    householdId: r.householdId,
    scope: r.scope,
    childId: r.childId,
    categoryId: r.categoryId,
    label: r.label,
    amount: parseFloat(r.amount),
    type: r.type,
    isActive: r.isActive,
    createdAt: r.createdAt,
  }));
}

export async function createFixedCost(
  householdId: string,
  input: CreateFixedCostInput,
): Promise<FixedCost> {
  const [row] = await db
    .insert(fixedCosts)
    .values({
      householdId,
      scope: input.scope,
      childId: input.childId ?? null,
      categoryId: input.categoryId ?? null,
      label: input.label,
      amount: String(input.amount),
      type: input.type,
    })
    .returning();

  return {
    id: row!.id,
    householdId: row!.householdId,
    scope: row!.scope,
    childId: row!.childId,
    categoryId: row!.categoryId,
    label: row!.label,
    amount: parseFloat(row!.amount),
    type: row!.type,
    isActive: row!.isActive,
    createdAt: row!.createdAt,
  };
}

export async function updateFixedCost(
  id: string,
  householdId: string,
  input: UpdateFixedCostInput,
): Promise<FixedCost | null> {
  const values: Partial<typeof fixedCosts.$inferInsert> = {};
  if (input.label !== undefined) values.label = input.label;
  if (input.amount !== undefined) values.amount = String(input.amount);
  if (input.categoryId !== undefined) values.categoryId = input.categoryId;
  if (input.isActive !== undefined) values.isActive = input.isActive;

  const [row] = await db
    .update(fixedCosts)
    .set(values)
    .where(and(eq(fixedCosts.id, id), eq(fixedCosts.householdId, householdId)))
    .returning();

  if (!row) return null;
  return {
    id: row.id,
    householdId: row.householdId,
    scope: row.scope,
    childId: row.childId,
    categoryId: row.categoryId,
    label: row.label,
    amount: parseFloat(row.amount),
    type: row.type,
    isActive: row.isActive,
    createdAt: row.createdAt,
  };
}

export async function deleteFixedCost(id: string, householdId: string): Promise<boolean> {
  // Soft delete — pause, don't destroy history
  const [row] = await db
    .update(fixedCosts)
    .set({ isActive: false })
    .where(and(eq(fixedCosts.id, id), eq(fixedCosts.householdId, householdId)))
    .returning({ id: fixedCosts.id });
  return !!row;
}

// ── Transfer Line Items ───────────────────────────────────────────────────────

export async function getTransferLineItems(householdId: string): Promise<TransferLineItem[]> {
  const rows = await db
    .select()
    .from(transferLineItems)
    .where(and(eq(transferLineItems.householdId, householdId), eq(transferLineItems.isActive, true)))
    .orderBy(desc(transferLineItems.createdAt));

  return rows.map((r) => ({
    id: r.id,
    householdId: r.householdId,
    label: r.label,
    amount: parseFloat(r.amount),
    isActive: r.isActive,
    createdAt: r.createdAt,
  }));
}

export async function createTransferLineItem(
  householdId: string,
  input: CreateTransferLineItemInput,
): Promise<TransferLineItem> {
  const [row] = await db
    .insert(transferLineItems)
    .values({ householdId, label: input.label, amount: String(input.amount) })
    .returning();

  return {
    id: row!.id,
    householdId: row!.householdId,
    label: row!.label,
    amount: parseFloat(row!.amount),
    isActive: row!.isActive,
    createdAt: row!.createdAt,
  };
}

export async function updateTransferLineItem(
  id: string,
  householdId: string,
  input: UpdateTransferLineItemInput,
): Promise<TransferLineItem | null> {
  const values: Partial<typeof transferLineItems.$inferInsert> = {};
  if (input.label !== undefined) values.label = input.label;
  if (input.amount !== undefined) values.amount = String(input.amount);
  if (input.isActive !== undefined) values.isActive = input.isActive;

  const [row] = await db
    .update(transferLineItems)
    .set(values)
    .where(and(eq(transferLineItems.id, id), eq(transferLineItems.householdId, householdId)))
    .returning();

  if (!row) return null;
  return {
    id: row.id,
    householdId: row.householdId,
    label: row.label,
    amount: parseFloat(row.amount),
    isActive: row.isActive,
    createdAt: row.createdAt,
  };
}

export async function deleteTransferLineItem(id: string, householdId: string): Promise<boolean> {
  const [row] = await db
    .update(transferLineItems)
    .set({ isActive: false })
    .where(and(eq(transferLineItems.id, id), eq(transferLineItems.householdId, householdId)))
    .returning({ id: transferLineItems.id });
  return !!row;
}

// ── Monthly Transfers ─────────────────────────────────────────────────────────

export async function getMonthlyTransfers(
  householdId: string,
  month: string,
): Promise<MonthlyTransfer[]> {
  const rows = await db
    .select()
    .from(monthlyTransfers)
    .where(
      and(eq(monthlyTransfers.householdId, householdId), eq(monthlyTransfers.month, month)),
    )
    .orderBy(desc(monthlyTransfers.createdAt));

  return rows.map((r) => ({
    id: r.id,
    householdId: r.householdId,
    month: r.month,
    fromUserId: r.fromUserId,
    toUserId: r.toUserId,
    amount: parseFloat(r.amount),
    note: r.note,
    createdAt: r.createdAt,
  }));
}

export async function createMonthlyTransfer(
  householdId: string,
  fromUserId: string,
  input: CreateMonthlyTransferInput,
): Promise<MonthlyTransfer> {
  const [row] = await db
    .insert(monthlyTransfers)
    .values({
      householdId,
      month: input.month,
      fromUserId,
      toUserId: input.toUserId,
      amount: String(input.amount),
      note: input.note ?? null,
    })
    .returning();

  return {
    id: row!.id,
    householdId: row!.householdId,
    month: row!.month,
    fromUserId: row!.fromUserId,
    toUserId: row!.toUserId,
    amount: parseFloat(row!.amount),
    note: row!.note,
    createdAt: row!.createdAt,
  };
}

// ── Settlement V2 ─────────────────────────────────────────────────────────────

export async function getSettlementV2(
  householdId: string,
  userId: string,
  month: string,
): Promise<SettlementV2> {
  const [y, m] = month.split("-").map(Number);
  const from = `${month}-01`;
  const lastDay = new Date(y!, m!, 0).getDate();
  const to = `${month}-${String(lastDay).padStart(2, "0")}`;

  // Members (with role to determine owner vs partner)
  const members = await db
    .select({ userId: member.userId, name: user.name, role: member.role })
    .from(member)
    .innerJoin(user, eq(member.userId, user.id))
    .where(eq(member.organizationId, householdId));
  const memberCount = members.length;

  const settings = await getOrCreateHouseholdSettings(householdId);
  const ownerMember = members.find((m) => m.role === "owner");
  // payerUserId = who pays all expenses upfront (defaults to owner if not set)
  const payerUserId = settings.payerUserId ?? ownerMember?.userId ?? userId;
  const isPayer = userId === payerUserId;
  // userSharePercent in settings = the PAYER's share (e.g. René pays 75%)
  const baseSharePercent = Number(settings.userSharePercent ?? 50);
  const userSharePercent = isPayer
    ? baseSharePercent          // payer's own share
    : 100 - baseSharePercent;  // debtor owes the remainder

  // Household expenses by user
  const expenseRows = await db
    .select({
      userId: transactions.userId,
      total: sql<string>`coalesce(sum(${transactions.amount}::numeric), '0')`,
    })
    .from(transactions)
    .where(
      and(
        eq(transactions.householdId, householdId),
        eq(transactions.scope, "household"),
        eq(transactions.type, "expense"),
        sql`${transactions.date} >= ${from}`,
        sql`${transactions.date} <= ${to}`,
      ),
    )
    .groupBy(transactions.userId);

  const paidByUser: Record<string, number> = {};
  let householdExpenses = 0;
  for (const row of expenseRows) {
    const amount = parseFloat(row.total);
    paidByUser[row.userId] = amount;
    householdExpenses += amount;
  }

  // Household income
  const [incomeRow] = await db
    .select({ total: sql<string>`coalesce(sum(${transactions.amount}::numeric), '0')` })
    .from(transactions)
    .where(
      and(
        eq(transactions.householdId, householdId),
        eq(transactions.scope, "household"),
        eq(transactions.type, "income"),
        sql`${transactions.date} >= ${from}`,
        sql`${transactions.date} <= ${to}`,
      ),
    );
  const householdIncome = parseFloat(incomeRow?.total ?? "0");

  const householdNet = householdExpenses - householdIncome;
  const perMemberShare = householdNet * (userSharePercent / 100);

  // Line items
  const lineItemRows = await db
    .select()
    .from(transferLineItems)
    .where(and(eq(transferLineItems.householdId, householdId), eq(transferLineItems.isActive, true)));

  const lineItems = lineItemRows.map((r) => ({
    id: r.id,
    label: r.label,
    amount: parseFloat(r.amount),
  }));
  const lineItemsTotal = lineItems.reduce((sum, li) => sum + li.amount, 0);

  // Already transferred this month (by me)
  const transferRows = await getMonthlyTransfers(householdId, month);
  const myTransfers = transferRows.filter((t) => t.fromUserId === userId);
  const alreadyTransferred = myTransfers.reduce((sum, t) => sum + t.amount, 0);

  // Only manual (non-fixed) expenses by the debtor count as "already paid by them"
  // Fixed costs are always attributed to the payer regardless of which userId booked them
  let myOwnExpenses = 0;
  if (!isPayer) {
    const [manualRow] = await db
      .select({ total: sql<string>`coalesce(sum(${transactions.amount}::numeric), '0')` })
      .from(transactions)
      .where(
        and(
          eq(transactions.householdId, householdId),
          eq(transactions.scope, "household"),
          eq(transactions.type, "expense"),
          eq(transactions.userId, userId),
          eq(transactions.isFixed, false),
          sql`${transactions.date} >= ${from}`,
          sql`${transactions.date} <= ${to}`,
        ),
      );
    myOwnExpenses = parseFloat(manualRow?.total ?? "0");
  }
  const totalOwed = perMemberShare + lineItemsTotal - myOwnExpenses;
  const remaining = totalOwed - alreadyTransferred;

  const membersResult = members.map((mem) => {
    const paid = paidByUser[mem.userId] ?? 0;
    const memIsPayer = mem.userId === payerUserId;
    const memSharePercent = memIsPayer
      ? baseSharePercent
      : 100 - baseSharePercent;
    const share = householdNet * (memSharePercent / 100);
    return { userId: mem.userId, name: mem.name, paid, owes: share - paid };
  });

  return {
    month,
    householdExpenses,
    householdIncome,
    householdNet,
    memberCount,
    perMemberShare,
    userSharePercent,
    payerUserId,
    isPayer,
    lineItems,
    lineItemsTotal,
    myOwnExpenses,
    transfers: transferRows,
    alreadyTransferred,
    totalOwed,
    remaining,
    members: membersResult,
  };
}

// ── Netto Month ───────────────────────────────────────────────────────────────

export async function getNettoMonth(
  householdId: string,
  userId: string,
  month: string,
): Promise<NettoMonth | null> {
  const [y, m] = month.split("-").map(Number);
  const from = `${month}-01`;
  const lastDay = new Date(y!, m!, 0).getDate();
  const to = `${month}-${String(lastDay).padStart(2, "0")}`;

  // Check if month has any transactions at all — return null for empty months
  const [countRow] = await db
    .select({ count: sql<string>`count(*)` })
    .from(transactions)
    .where(
      and(
        eq(transactions.householdId, householdId),
        sql`${transactions.date} >= ${from}`,
        sql`${transactions.date} <= ${to}`,
      ),
    );
  const txCount = parseInt(countRow?.count ?? "0", 10);
  if (txCount === 0) return null;

  const settings = await getOrCreateHouseholdSettings(householdId);
  const userShare = settings.userSharePercent / 100;
  const childShare = settings.splitChildCosts ? userShare : 0;

  // Income: all scopes (household income is the user's own income bookings)
  const incomeRows = await db
    .select({
      scope: transactions.scope,
      total: sql<string>`coalesce(sum(${transactions.amount}::numeric), '0')`,
    })
    .from(transactions)
    .where(
      and(
        eq(transactions.householdId, householdId),
        eq(transactions.userId, userId),
        eq(transactions.type, "income"),
        sql`${transactions.date} >= ${from}`,
        sql`${transactions.date} <= ${to}`,
      ),
    )
    .groupBy(transactions.scope);

  const incomeByScope = incomeRows.map((r) => ({
    scope: r.scope,
    label: r.scope === "household" ? "Haushalt" : r.scope === "private" ? "Privat" : "Kinder",
    amount: parseFloat(r.total),
  }));
  const totalIncome = incomeByScope.reduce((sum, s) => sum + s.amount, 0);

  // Household expenses × userShare
  const [hhExpRow] = await db
    .select({ total: sql<string>`coalesce(sum(${transactions.amount}::numeric), '0')` })
    .from(transactions)
    .where(
      and(
        eq(transactions.householdId, householdId),
        eq(transactions.scope, "household"),
        eq(transactions.type, "expense"),
        sql`${transactions.date} >= ${from}`,
        sql`${transactions.date} <= ${to}`,
      ),
    );
  const householdExpensesShare = parseFloat(hhExpRow?.total ?? "0") * userShare;

  // Private expenses (only mine)
  const [privExpRow] = await db
    .select({ total: sql<string>`coalesce(sum(${transactions.amount}::numeric), '0')` })
    .from(transactions)
    .where(
      and(
        eq(transactions.householdId, householdId),
        eq(transactions.userId, userId),
        eq(transactions.scope, "private"),
        eq(transactions.type, "expense"),
        sql`${transactions.date} >= ${from}`,
        sql`${transactions.date} <= ${to}`,
      ),
    );
  const privateExpenses = parseFloat(privExpRow?.total ?? "0");

  // Child expenses × childShare
  const [childExpRow] = await db
    .select({ total: sql<string>`coalesce(sum(${transactions.amount}::numeric), '0')` })
    .from(transactions)
    .where(
      and(
        eq(transactions.householdId, householdId),
        eq(transactions.scope, "child"),
        eq(transactions.type, "expense"),
        sql`${transactions.date} >= ${from}`,
        sql`${transactions.date} <= ${to}`,
      ),
    );
  const childExpensesShare = parseFloat(childExpRow?.total ?? "0") * childShare;

  // Fixed transfer line items (only included when month has transactions)
  const lineItemRows = await db
    .select()
    .from(transferLineItems)
    .where(and(eq(transferLineItems.householdId, householdId), eq(transferLineItems.isActive, true)));
  const lineItemsTotal = lineItemRows.reduce((sum, r) => sum + parseFloat(r.amount), 0);

  const totalExpenses = householdExpensesShare + privateExpenses + childExpensesShare + lineItemsTotal;

  return {
    month,
    totalIncome,
    incomeByScope,
    totalExpenses,
    netto: totalIncome - totalExpenses,
  };
}
