import { db, eq, and, desc, sql } from "@haushaltsApp/db";
import { trips, tripExpenses } from "@haushaltsApp/db/schema";
import { member, user } from "@haushaltsApp/db/schema";
import type {
  CreateTripInput,
  UpdateTripInput,
  CreateTripExpenseInput,
  UpdateTripExpenseInput,
  TripCategory,
} from "@haushaltsApp/shared/schemas/trips";
import { TRIP_CATEGORIES } from "@haushaltsApp/shared/schemas/trips";

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

export type TripRow = typeof trips.$inferSelect;
export type TripExpenseRow = typeof tripExpenses.$inferSelect;

export type TripWithSpent = {
  id: string;
  householdId: string;
  name: string;
  destination: string | null;
  budget: number;
  startDate: string;
  endDate: string;
  status: string;
  spent: number;
  remaining: number;
  settlementFromUserId: string | null;
  settlementToUserId: string | null;
  settlementAmount: number | null;
  settledAt: string | null;
  createdAt: Date;
  updatedAt: Date;
};

export type TripSettlement = {
  total: number;
  fairShare: number;
  balances: Array<{ userId: string; name: string; paid: number; fairShare: number; balance: number }>;
  settlement: {
    from: string;
    fromName: string;
    to: string;
    toName: string;
    amount: number;
  } | null;
};

export type TripExpense = {
  id: string;
  tripId: string;
  householdId: string;
  label: string;
  amount: number;
  category: string;
  paidBy: string;
  date: string;
  note: string | null;
  createdAt: Date;
};

export type TripSummary = {
  trip: TripWithSpent;
  totalSpent: number;
  remaining: number;
  byCategory: Record<TripCategory, number>;
};

// ── Helpers ───────────────────────────────────────────────────────────────────

async function getSpentAmount(tripId: string): Promise<number> {
  const result = await db
    .select({ total: sql<string>`coalesce(sum(${tripExpenses.amount}), '0')` })
    .from(tripExpenses)
    .where(eq(tripExpenses.tripId, tripId));
  return parseFloat(result[0]?.total ?? "0");
}

function mapTripRow(row: TripRow, spent: number): TripWithSpent {
  const budget = parseFloat(row.budget);
  return {
    id: row.id,
    householdId: row.householdId,
    name: row.name,
    destination: row.destination,
    budget,
    startDate: row.startDate,
    endDate: row.endDate,
    status: row.status,
    spent,
    remaining: budget - spent,
    settlementFromUserId: row.settlementFromUserId ?? null,
    settlementToUserId: row.settlementToUserId ?? null,
    settlementAmount: row.settlementAmount ? parseFloat(row.settlementAmount) : null,
    settledAt: row.settledAt ?? null,
    createdAt: row.createdAt,
    updatedAt: row.updatedAt,
  };
}

function mapExpenseRow(row: TripExpenseRow): TripExpense {
  return {
    id: row.id,
    tripId: row.tripId,
    householdId: row.householdId,
    label: row.label,
    amount: parseFloat(row.amount),
    category: row.category,
    paidBy: row.paidBy,
    date: row.date,
    note: row.note,
    createdAt: row.createdAt,
  };
}

// ── Settlement helpers ─────────────────────────────────────────────────────────

export function calculateTripSettlement(
  expenses: TripExpenseRow[],
  members: Array<{ userId: string; name: string }>,
): TripSettlement {
  const total = expenses.reduce((sum, e) => sum + parseFloat(e.amount), 0);
  const fairShare = members.length > 0 ? total / members.length : 0;

  const paidByUser: Record<string, number> = {};
  for (const expense of expenses) {
    paidByUser[expense.paidBy] = (paidByUser[expense.paidBy] ?? 0) + parseFloat(expense.amount);
  }

  const balances = members.map((m) => {
    const paid = paidByUser[m.userId] ?? 0;
    return {
      userId: m.userId,
      name: m.name,
      paid,
      fairShare,
      balance: paid - fairShare,
    };
  });

  const debtor = balances.find((b) => b.balance < 0) ?? null;
  const creditor = balances.find((b) => b.balance > 0) ?? null;

  let settlement: TripSettlement["settlement"] = null;
  if (debtor && creditor && Math.abs(debtor.balance) >= 0.01) {
    settlement = {
      from: debtor.userId,
      fromName: debtor.name,
      to: creditor.userId,
      toName: creditor.name,
      amount: Math.round(Math.abs(debtor.balance) * 100) / 100,
    };
  }

  return { total, fairShare, balances, settlement };
}

export async function getTripSettlementPreview(
  tripId: string,
  householdId: string,
): Promise<TripSettlement | null> {
  const [tripRow] = await db
    .select()
    .from(trips)
    .where(and(eq(trips.id, tripId), eq(trips.householdId, householdId)));

  if (!tripRow) return null;

  const expenses = await db
    .select()
    .from(tripExpenses)
    .where(eq(tripExpenses.tripId, tripId));

  const members = await db
    .select({ userId: member.userId, name: user.name })
    .from(member)
    .innerJoin(user, eq(member.userId, user.id))
    .where(eq(member.organizationId, householdId));

  return calculateTripSettlement(expenses, members);
}

// ── Service functions ─────────────────────────────────────────────────────────

export async function getTrips(householdId: string): Promise<TripWithSpent[]> {
  const rows = await db
    .select()
    .from(trips)
    .where(eq(trips.householdId, householdId))
    .orderBy(desc(trips.startDate));

  return Promise.all(
    rows.map(async (row) => {
      const spent = await getSpentAmount(row.id);
      return mapTripRow(row, spent);
    }),
  );
}

export async function createTrip(householdId: string, input: CreateTripInput): Promise<TripWithSpent> {
  const [row] = await db
    .insert(trips)
    .values({
      householdId,
      name: input.name,
      destination: input.destination ?? null,
      budget: String(input.budget),
      startDate: input.startDate,
      endDate: input.endDate,
    })
    .returning();

  return mapTripRow(row!, 0);
}

export async function updateTrip(
  id: string,
  householdId: string,
  input: UpdateTripInput,
): Promise<TripWithSpent | null> {
  const updateValues: Partial<typeof trips.$inferInsert> = {};
  if (input.name !== undefined) updateValues.name = input.name;
  if (input.destination !== undefined) updateValues.destination = input.destination ?? null;
  if (input.budget !== undefined) updateValues.budget = String(input.budget);
  if (input.startDate !== undefined) updateValues.startDate = input.startDate;
  if (input.endDate !== undefined) updateValues.endDate = input.endDate;

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

  if (!row) return null;
  const spent = await getSpentAmount(id);
  return mapTripRow(row, spent);
}

export async function deleteTrip(id: string, householdId: string): Promise<boolean> {
  // Check for expenses before deleting
  const [expenseCheck] = await db
    .select({ id: tripExpenses.id })
    .from(tripExpenses)
    .where(eq(tripExpenses.tripId, id))
    .limit(1);

  if (expenseCheck) {
    throw new Error("Has expenses");
  }

  const result = await db
    .delete(trips)
    .where(and(eq(trips.id, id), eq(trips.householdId, householdId)))
    .returning({ id: trips.id });

  return result.length > 0;
}

export async function completeTrip(id: string, householdId: string): Promise<TripWithSpent | null> {
  const expenses = await db
    .select()
    .from(tripExpenses)
    .where(eq(tripExpenses.tripId, id));

  const members = await db
    .select({ userId: member.userId, name: user.name })
    .from(member)
    .innerJoin(user, eq(member.userId, user.id))
    .where(eq(member.organizationId, householdId));

  const settlementResult = calculateTripSettlement(expenses, members);
  const { settlement } = settlementResult;

  const [row] = await db
    .update(trips)
    .set({
      status: "completed",
      settlementFromUserId: settlement?.from ?? null,
      settlementToUserId: settlement?.to ?? null,
      settlementAmount: settlement ? String(settlement.amount) : null,
      settledAt: new Date().toISOString(),
    })
    .where(and(eq(trips.id, id), eq(trips.householdId, householdId)))
    .returning();

  if (!row) return null;
  const spent = await getSpentAmount(id);
  return mapTripRow(row, spent);
}

export async function getTripExpenses(tripId: string, householdId: string): Promise<TripExpense[]> {
  // Verify trip belongs to household
  const [trip] = await db
    .select({ id: trips.id })
    .from(trips)
    .where(and(eq(trips.id, tripId), eq(trips.householdId, householdId)));

  if (!trip) return [];

  const rows = await db
    .select()
    .from(tripExpenses)
    .where(eq(tripExpenses.tripId, tripId))
    .orderBy(desc(tripExpenses.date));

  return rows.map(mapExpenseRow);
}

export async function createTripExpense(
  tripId: string,
  householdId: string,
  input: CreateTripExpenseInput,
): Promise<TripExpense> {
  // Verify trip belongs to household
  const [trip] = await db
    .select({ id: trips.id })
    .from(trips)
    .where(and(eq(trips.id, tripId), eq(trips.householdId, householdId)));

  if (!trip) throw new Error("Trip not found");

  const [row] = await db
    .insert(tripExpenses)
    .values({
      tripId,
      householdId,
      label: input.label,
      amount: String(input.amount),
      category: input.category ?? "sonstiges",
      paidBy: input.paidBy,
      date: input.date,
      note: input.note ?? null,
    })
    .returning();

  return mapExpenseRow(row!);
}

export async function updateTripExpense(
  expenseId: string,
  tripId: string,
  householdId: string,
  input: UpdateTripExpenseInput,
): Promise<TripExpense | null> {
  // Verify trip belongs to household
  const [trip] = await db
    .select({ id: trips.id })
    .from(trips)
    .where(and(eq(trips.id, tripId), eq(trips.householdId, householdId)));

  if (!trip) return null;

  const updateValues: Partial<typeof tripExpenses.$inferInsert> = {};
  if (input.label !== undefined) updateValues.label = input.label;
  if (input.amount !== undefined) updateValues.amount = String(input.amount);
  if (input.category !== undefined) updateValues.category = input.category;
  if (input.paidBy !== undefined) updateValues.paidBy = input.paidBy;
  if (input.date !== undefined) updateValues.date = input.date;
  if (input.note !== undefined) updateValues.note = input.note ?? null;

  const [row] = await db
    .update(tripExpenses)
    .set(updateValues)
    .where(and(eq(tripExpenses.id, expenseId), eq(tripExpenses.tripId, tripId)))
    .returning();

  if (!row) return null;
  return mapExpenseRow(row);
}

export async function deleteTripExpense(
  expenseId: string,
  tripId: string,
  householdId: string,
): Promise<boolean> {
  // Verify trip belongs to household
  const [trip] = await db
    .select({ id: trips.id })
    .from(trips)
    .where(and(eq(trips.id, tripId), eq(trips.householdId, householdId)));

  if (!trip) return false;

  const result = await db
    .delete(tripExpenses)
    .where(and(eq(tripExpenses.id, expenseId), eq(tripExpenses.tripId, tripId)))
    .returning({ id: tripExpenses.id });

  return result.length > 0;
}

export async function getTripSummary(tripId: string, householdId: string): Promise<TripSummary | null> {
  const [tripRow] = await db
    .select()
    .from(trips)
    .where(and(eq(trips.id, tripId), eq(trips.householdId, householdId)));

  if (!tripRow) return null;

  const expenses = await db
    .select()
    .from(tripExpenses)
    .where(eq(tripExpenses.tripId, tripId));

  const byCategory = Object.fromEntries(
    TRIP_CATEGORIES.map((cat) => [cat, 0]),
  ) as Record<TripCategory, number>;

  let totalSpent = 0;
  for (const expense of expenses) {
    const amount = parseFloat(expense.amount);
    totalSpent += amount;
    const cat = expense.category as TripCategory;
    if (cat in byCategory) {
      byCategory[cat] += amount;
    } else {
      byCategory["sonstiges"] += amount;
    }
  }

  const budget = parseFloat(tripRow.budget);
  const trip = mapTripRow(tripRow, totalSpent);

  return {
    trip,
    totalSpent,
    remaining: budget - totalSpent,
    byCategory,
  };
}
