import { db, eq, and, or, desc, gte, lte, sql } from "@haushaltsApp/db";
import { categories, fixedCosts, households, transactions } from "@haushaltsApp/db/schema";
import type {
  CreateTransactionInput,
  TransactionFilters,
  UpdateTransactionInput,
} from "@haushaltsApp/shared/schemas/transaction";

export async function getTransactions(
  householdId: string,
  userId: string,
  filters: TransactionFilters = { limit: 50, offset: 0 },
) {
  const conditions = [eq(transactions.householdId, householdId)];

  if (filters.categoryId) {
    conditions.push(eq(transactions.categoryId, filters.categoryId));
  }
  if (filters.type) {
    conditions.push(eq(transactions.type, filters.type));
  }
  if (filters.from) {
    conditions.push(gte(transactions.date, filters.from.split("T")[0] ?? filters.from));
  }
  if (filters.to) {
    conditions.push(lte(transactions.date, filters.to.split("T")[0] ?? filters.to));
  }
  if (filters.childId) {
    conditions.push(eq(transactions.childId, filters.childId));
  }

  // scope filter — private transactions only visible to their creator
  if (filters.scope === "private") {
    conditions.push(eq(transactions.scope, "private"));
    conditions.push(eq(transactions.userId, userId));
  } else if (filters.scope) {
    conditions.push(eq(transactions.scope, filters.scope));
  } else {
    // default: exclude private transactions from other users
    conditions.push(
      or(
        eq(transactions.scope, "household"),
        eq(transactions.scope, "child"),
        and(eq(transactions.scope, "private"), eq(transactions.userId, userId)),
      )!,
    );
  }

  return db
    .select({
      id: transactions.id,
      householdId: transactions.householdId,
      userId: transactions.userId,
      categoryId: transactions.categoryId,
      childId: transactions.childId,
      scope: transactions.scope,
      amount: transactions.amount,
      currency: transactions.currency,
      type: transactions.type,
      isFixed: transactions.isFixed,
      isCarryOver: transactions.isCarryOver,
      description: transactions.description,
      merchant: transactions.merchant,
      date: transactions.date,
      receiptImageUrl: transactions.receiptImageUrl,
      createdAt: transactions.createdAt,
      updatedAt: transactions.updatedAt,
      categoryName: categories.name,
      categoryIcon: categories.icon,
      categoryColor: categories.color,
    })
    .from(transactions)
    .leftJoin(categories, eq(transactions.categoryId, categories.id))
    .where(and(...conditions))
    .orderBy(desc(transactions.date))
    .limit(filters.limit)
    .offset(filters.offset);
}

export async function getTransactionById(id: string, householdId: string) {
  const [transaction] = await db
    .select()
    .from(transactions)
    .where(and(eq(transactions.id, id), eq(transactions.householdId, householdId)));
  return transaction ?? null;
}

export async function createTransaction(
  householdId: string,
  userId: string,
  input: CreateTransactionInput,
) {
  const household = await db.query.households.findFirst({
    where: eq(households.id, householdId),
  });
  if (!household) {
    throw new Error("Household not found — run onboarding first");
  }

  const [transaction] = await db
    .insert(transactions)
    .values({
      householdId,
      userId,
      categoryId: input.categoryId ?? null,
      childId: input.childId ?? null,
      scope: input.scope ?? "household",
      amount: String(input.amount),
      currency: "EUR",
      type: input.type,
      isFixed: input.isFixed ?? false,
      description: input.description ?? null,
      merchant: input.merchant ?? null,
      date: input.date.split("T")[0] ?? input.date,
    })
    .returning();
  return transaction;
}

export async function updateTransaction(
  id: string,
  householdId: string,
  input: UpdateTransactionInput,
) {
  const values: Record<string, unknown> = {};
  if (input.amount !== undefined) values.amount = String(input.amount);
  if (input.type !== undefined) values.type = input.type;
  if (input.scope !== undefined) values.scope = input.scope;
  if (input.categoryId !== undefined) values.categoryId = input.categoryId;
  if (input.childId !== undefined) values.childId = input.childId;
  if (input.isFixed !== undefined) values.isFixed = input.isFixed;
  if (input.description !== undefined) values.description = input.description;
  if (input.merchant !== undefined) values.merchant = input.merchant;
  if (input.date !== undefined) values.date = input.date.split("T")[0] ?? input.date;

  const [transaction] = await db
    .update(transactions)
    .set(values)
    .where(and(eq(transactions.id, id), eq(transactions.householdId, householdId)))
    .returning();
  return transaction ?? null;
}

export async function deleteTransaction(id: string, householdId: string) {
  const [transaction] = await db
    .delete(transactions)
    .where(and(eq(transactions.id, id), eq(transactions.householdId, householdId)))
    .returning();
  return transaction ?? null;
}

// 11a — activate fixed transactions for a month (reads from fixed_costs templates)
export async function activateFixedTransactions(
  householdId: string,
  userId: string,
  month: string,  // "YYYY-MM"
  scope: "household" | "private" | "child",
  childId?: string,
) {
  // Guard: never auto-fill past months
  const now = new Date();
  const currentMonthStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
  if (month < currentMonthStr) return { created: 0 };

  const [y, m] = month.split("-").map(Number);
  const curFrom = `${month}-01`;
  const curLastDay = new Date(y!, m!, 0).getDate();
  const curTo = `${month}-${String(curLastDay).padStart(2, "0")}`;

  // Load active fixed cost templates for this scope
  const templateConditions = [
    eq(fixedCosts.householdId, householdId),
    eq(fixedCosts.scope, scope),
    eq(fixedCosts.isActive, true),
  ];
  if (childId) templateConditions.push(eq(fixedCosts.childId, childId));

  const templates = await db.select().from(fixedCosts).where(and(...templateConditions));
  if (templates.length === 0) return { created: 0 };

  // Load already-activated fixed transactions for this month+scope to avoid duplicates
  const existingConditions = [
    eq(transactions.householdId, householdId),
    eq(transactions.isFixed, true),
    eq(transactions.scope, scope),
    gte(transactions.date, curFrom),
    lte(transactions.date, curTo),
  ];
  if (childId) existingConditions.push(eq(transactions.childId, childId));
  if (scope === "private") existingConditions.push(eq(transactions.userId, userId));

  const existing = await db
    .select({ description: transactions.description })
    .from(transactions)
    .where(and(...existingConditions));
  const activatedLabels = new Set(existing.map((e) => e.description));

  // Only insert templates not yet activated
  const rows = templates
    .filter((t) => !activatedLabels.has(t.label))
    .map((t) => ({
      householdId,
      userId,
      categoryId: t.categoryId,
      childId: t.childId,
      scope: t.scope,
      amount: t.amount,
      type: t.type,
      isFixed: true as const,
      isCarryOver: false as const,
      description: t.label,
      date: curFrom,
    }));

  if (rows.length === 0) return { created: 0 };
  await db.insert(transactions).values(rows);
  return { created: rows.length };
}

// 11b — carry-over: create a carry-over transaction in the next month
export async function carryOverBalance(
  householdId: string,
  userId: string,
  fromMonth: string,  // "YYYY-MM"
  toMonth: string,    // "YYYY-MM"
  scope: "household" | "private" | "child",
  childId?: string,
) {
  // Guard: idempotent — check if carry-over already exists for toMonth+scope+childId
  const [ty, tm] = toMonth.split("-").map(Number);
  const toFrom = `${toMonth}-01`;
  const toLastDay = new Date(ty!, tm!, 0).getDate();
  const toTo = `${toMonth}-${String(toLastDay).padStart(2, "0")}`;

  const existingConditions = [
    eq(transactions.householdId, householdId),
    eq(transactions.isCarryOver, true),
    eq(transactions.scope, scope),
    gte(transactions.date, toFrom),
    lte(transactions.date, toTo),
  ];
  if (childId) existingConditions.push(eq(transactions.childId, childId));

  const existing = await db.select({ id: transactions.id }).from(transactions).where(and(...existingConditions));
  if (existing.length > 0) {
    return { alreadyExists: true, transaction: null };
  }

  // Calculate balance for fromMonth
  const [fy, fm] = fromMonth.split("-").map(Number);
  const fromFrom = `${fromMonth}-01`;
  const fromLastDay = new Date(fy!, fm!, 0).getDate();
  const fromTo = `${fromMonth}-${String(fromLastDay).padStart(2, "0")}`;

  const balanceConditions = [
    eq(transactions.householdId, householdId),
    eq(transactions.scope, scope),
    gte(transactions.date, fromFrom),
    lte(transactions.date, fromTo),
  ];
  if (childId) balanceConditions.push(eq(transactions.childId, childId));
  if (scope === "private") balanceConditions.push(eq(transactions.userId, userId));

  const result = await db.select({
    type: transactions.type,
    total: sql<string>`sum(${transactions.amount}::numeric)`,
  }).from(transactions).where(and(...balanceConditions)).groupBy(transactions.type);

  let income = 0;
  let expense = 0;
  for (const row of result) {
    if (row.type === "income") income = Number(row.total ?? 0);
    else expense = Number(row.total ?? 0);
  }
  const balance = income - expense;

  if (Math.abs(balance) < 0.01) {
    return { alreadyExists: false, transaction: null, balance: 0 };
  }

  // Month name in German for description
  const months = ["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"];
  const fromMonthName = `${months[fm! - 1]} ${fy}`;
  const description = `Übertrag ${fromMonthName}`;

  // balance > 0 = net income; balance < 0 = net expense
  // We carry over as a transaction that brings the slate even
  const type = balance > 0 ? "expense" : "income";  // net surplus → carry as expense in next month; net deficit → carry as income
  const amount = Math.abs(balance);

  const [tx] = await db.insert(transactions).values({
    householdId,
    userId,
    categoryId: null,
    childId: childId ?? null,
    scope,
    amount: String(amount),
    currency: "EUR",
    type,
    isFixed: false,
    isCarryOver: true,
    description,
    date: toFrom,  // 1st of toMonth
  }).returning();

  return { alreadyExists: false, transaction: tx, balance };
}

export async function getTransactionSummary(
  householdId: string,
  userId: string,
  month: Date,
  scope?: "household" | "private" | "child",
) {
  const year = month.getFullYear();
  const monthNum = month.getMonth() + 1;
  const from = `${year}-${String(monthNum).padStart(2, "0")}-01`;
  const lastDay = new Date(year, monthNum, 0).getDate();
  const to = `${year}-${String(monthNum).padStart(2, "0")}-${String(lastDay).padStart(2, "0")}`;

  const conditions = [
    eq(transactions.householdId, householdId),
    gte(transactions.date, from),
    lte(transactions.date, to),
  ];

  if (scope === "private") {
    conditions.push(eq(transactions.scope, "private"));
    conditions.push(eq(transactions.userId, userId));
  } else if (scope) {
    conditions.push(eq(transactions.scope, scope));
  } else {
    conditions.push(
      or(
        eq(transactions.scope, "household"),
        eq(transactions.scope, "child"),
        and(eq(transactions.scope, "private"), eq(transactions.userId, userId)),
      )!,
    );
  }

  const result = await db
    .select({
      type: transactions.type,
      total: sql<string>`sum(${transactions.amount}::numeric)`,
    })
    .from(transactions)
    .where(and(...conditions))
    .groupBy(transactions.type);

  let income = 0;
  let expense = 0;
  for (const row of result) {
    if (row.type === "income") income = Number(row.total ?? 0);
    else expense = Number(row.total ?? 0);
  }

  return { income, expense, balance: income - expense };
}
