import { z } from "zod";
import { prisma } from "../../lib/prisma";
import { PaymentStatus } from "../../generated/prisma/client";

export const createPeriodicExpenseSchema = z.object({
  description: z.string().min(1).max(50),
  defaultDueDay: z.number().int().min(1).max(31),
  defaultAmount: z.number().positive(),
  periods: z.array(z.number().int().min(1).max(12)).min(1),
});

export const updatePeriodicExpenseSchema = createPeriodicExpenseSchema;

export const createNonPeriodicExpenseSchema = z.object({
  description: z.string().min(1).max(50),
  amount: z.number().positive(),
  dueDate: z.string().datetime(),
});

export const payExpenseSchema = z.object({
  amountPayed: z.number().positive(),
  paymentDate: z.string().datetime().optional(),
});

function lastDayOfMonth(year: number, month: number): number {
  return new Date(year, month, 0).getDate();
}

export function buildDueDate(year: number, month: number, dueDay: number): Date {
  const clampedDay = Math.min(dueDay, lastDayOfMonth(year, month));
  return new Date(Date.UTC(year, month - 1, clampedDay, 0, 0, 0, 0));
}

export function getCurrentYearMonthUTC(): { year: number; month: number } {
  const now = new Date();
  return { year: now.getUTCFullYear(), month: now.getUTCMonth() + 1 };
}

export async function listPeriodicExpenses() {
  return prisma.periodicExpense.findMany({
    where: { isDeleted: false },
    orderBy: { description: "asc" },
  });
}

export async function createPeriodicExpense(data: z.infer<typeof createPeriodicExpenseSchema>) {
  const result = await prisma.periodicExpense.create({ data });
  const { year, month } = getCurrentYearMonthUTC();
  const currentMonthApplicable = data.periods.includes(month);
  return { ...result, currentMonthApplicable };
}

export async function updatePeriodicExpense(id: number, data: z.infer<typeof updatePeriodicExpenseSchema>) {
  return prisma.periodicExpense.update({ where: { id }, data });
}

export async function softDeletePeriodicExpense(id: number) {
  return prisma.periodicExpense.update({
    where: { id },
    data: { isDeleted: true },
  });
}

export async function generateMonthlyExpense(id: number) {
  const periodic = await prisma.periodicExpense.findUniqueOrThrow({ where: { id } });
  const { year, month } = getCurrentYearMonthUTC();

  const existing = await prisma.expense.findFirst({
    where: { periodicExpenseId: id, year, month },
  });
  if (existing) return existing;

  const dueDate = buildDueDate(year, month, periodic.defaultDueDay);
  return prisma.expense.create({
    data: {
      description: periodic.description,
      periodicExpenseId: id,
      year,
      month,
      amount: periodic.defaultAmount,
      dueDate,
      status: PaymentStatus.PENDING,
    },
  });
}

export async function listExpenses(params: {
  status?: string;
  page?: number;
  pageSize?: number;
  periodicExpenseId?: number;
  search?: string;
}) {
  const { status, page = 1, pageSize = 10, periodicExpenseId, search } = params;
  const where: Record<string, unknown> = {};
  if (status === "PENDING" || status === "PAYED") {
    where.status = status;
  }
  if (periodicExpenseId !== undefined) {
    where.periodicExpenseId = periodicExpenseId;
  }
  if (search) {
    where.description = { contains: search, mode: "insensitive" };
  }
  const [data, total] = await Promise.all([
    prisma.expense.findMany({
      where,
      include: { periodicExpense: true },
      orderBy: { dueDate: "desc" },
      skip: (page - 1) * pageSize,
      take: pageSize,
    }),
    prisma.expense.count({ where }),
  ]);
  return { data, total, page, pageSize };
}

export async function createNonPeriodicExpense(data: z.infer<typeof createNonPeriodicExpenseSchema>) {
  const dueDate = new Date(data.dueDate);
  const year = dueDate.getUTCFullYear();
  const month = dueDate.getUTCMonth() + 1;
  return prisma.expense.create({
    data: {
      description: data.description,
      amount: data.amount,
      amountPayed: data.amount,
      dueDate,
      paymentDate: dueDate,
      year,
      month,
      status: PaymentStatus.PAYED,
    },
  });
}

export async function payExpense(id: number, data: z.infer<typeof payExpenseSchema>) {
  const paymentDate = data.paymentDate ? new Date(data.paymentDate) : new Date();
  return prisma.expense.update({
    where: { id },
    data: {
      status: PaymentStatus.PAYED,
      amountPayed: data.amountPayed,
      paymentDate,
    },
  });
}

export async function getMonthlyPayedTotal(year: number, month: number) {
  const expenses = await prisma.expense.findMany({
    where: { year, month, status: PaymentStatus.PAYED },
    select: { amountPayed: true },
  });
  const total = expenses.reduce((sum, e) => sum + Number(e.amountPayed ?? 0), 0);
  return { total };
}

export async function getMonthlyTotals(year: number, month: number) {
  const [payed, pending] = await Promise.all([
    prisma.expense.findMany({
      where: { year, month, status: PaymentStatus.PAYED },
      select: { amountPayed: true },
    }),
    prisma.expense.findMany({
      where: { year, month, status: PaymentStatus.PENDING },
      select: { amount: true },
    }),
  ]);
  return {
    payed: payed.reduce((sum, e) => sum + Number(e.amountPayed ?? 0), 0),
    pending: pending.reduce((sum, e) => sum + Number(e.amount), 0),
  };
}

export async function getTotalPending() {
  const expenses = await prisma.expense.findMany({
    where: { status: PaymentStatus.PENDING },
    select: { amount: true },
  });
  const total = expenses.reduce((sum, e) => sum + Number(e.amount), 0);
  return { total };
}

export async function generateExpensesForCurrentMonth() {
  const { year, month } = getCurrentYearMonthUTC();

  const periodics = await prisma.periodicExpense.findMany({
    where: { isDeleted: false, periods: { has: month } },
  });

  const results: Array<{ id: number; status: string }> = [];
  for (const periodic of periodics) {
    const existing = await prisma.expense.findFirst({
      where: { periodicExpenseId: periodic.id, year, month },
    });
    if (existing) {
      results.push({ id: periodic.id, status: "already_exists" });
      continue;
    }
    const dueDate = buildDueDate(year, month, periodic.defaultDueDay);
    await prisma.expense.create({
      data: {
        description: periodic.description,
        periodicExpenseId: periodic.id,
        year,
        month,
        amount: periodic.defaultAmount,
        dueDate,
        status: PaymentStatus.PENDING,
      },
    });
    results.push({ id: periodic.id, status: "created" });
  }
  return results;
}
