import { PaymentMealType, PersonStatus, ReplacementItem } from "@prisma/client";
import { format, isWithinInterval } from "date-fns";
import { prisma } from "@/lib/prisma";
import { DELIVERY_DAY_INDEXES } from "@/lib/constants";
import { endOfDay, parseDateKey, startOfDay, toDateKey } from "@/lib/utils";

export type TodayEntry = {
  personId: number;
  name: string;
  quantity?: number;
  note?: string;
};

function isWithinLeave(start: Date | null, end: Date | null, date: Date) {
  if (!start || !end) {
    return false;
  }

  return isWithinInterval(date, { start, end });
}

function isDeliveryDay(date: Date) {
  return DELIVERY_DAY_INDEXES.includes(date.getDay());
}

export async function getBreakStatus(date: Date) {
  const dayStart = startOfDay(date);
  const dayEnd = endOfDay(date);

  const breakPeriod = await prisma.breakPeriod.findFirst({
    where: {
      startDate: { lte: dayEnd },
      endDate: { gte: dayStart }
    },
    orderBy: { startDate: "asc" }
  });

  return breakPeriod;
}

export async function getTodayData(date: Date = new Date()) {
  const dayStart = startOfDay(date);
  const dayEnd = endOfDay(date);
  const [people, replacements, recentActivity, breakPeriod] = await Promise.all([
    prisma.person.findMany({
      include: {
        dailyOverrides: {
          where: {
            date: {
              gte: dayStart,
              lte: dayEnd
            }
          }
        }
      },
      orderBy: { name: "asc" }
    }),
    prisma.replacement.findMany({
      where: {
        date: {
          gte: dayStart,
          lte: dayEnd
        }
      },
      orderBy: { createdAt: "desc" }
    }),
    prisma.activityLog.findMany({
      where: {
        type: {
          in: ["person_added", "absence_marked", "replacement_added", "payment_recorded", "person_updated"]
        }
      },
      orderBy: { createdAt: "desc" },
      take: 8
    }),
    getBreakStatus(date)
  ]);

  const absentToday: TodayEntry[] = [];
  const lunchList: TodayEntry[] = [];
  const mandaziList: TodayEntry[] = [];

  for (const person of people) {
    const override = person.dailyOverrides[0];
    const paused = person.status === PersonStatus.paused;
    const onLeave = isWithinLeave(person.leaveStart, person.leaveEnd, date);
    const absent = Boolean(override?.isAbsent);
    const note = paused ? "paused" : onLeave ? "on leave" : absent ? "absent today" : undefined;

    if (absent) {
      absentToday.push({
        personId: person.id,
        name: person.name,
        note
      });
    }

    const eligible = !paused && !onLeave && !absent;

    if (eligible && person.takesLunch) {
      lunchList.push({
        personId: person.id,
        name: person.name
      });
    }

    if (eligible && person.takesMandazi) {
      mandaziList.push({
        personId: person.id,
        name: person.name,
        quantity: override?.mandaziQtyOverride ?? person.defaultMandaziQty
      });
    }
  }

  return {
    date,
    isDeliveryDay: isDeliveryDay(date),
    breakPeriod,
    lunchList,
    mandaziList,
    absentToday,
    replacements,
    recentActivity,
    headerLabel: breakPeriod
      ? "On break"
      : isDeliveryDay(date)
        ? `Today: ${format(date, "EEEE")}`
        : "No delivery today"
  };
}

export async function processDailyPaymentUsage(date: Date = new Date()) {
  const breakPeriod = await getBreakStatus(date);

  if (!isDeliveryDay(date) || breakPeriod) {
    return;
  }

  const marker = `delivery_processed:${format(date, "yyyy-MM-dd")}`;
  const existing = await prisma.activityLog.findFirst({
    where: {
      type: marker
    }
  });

  if (existing) {
    return;
  }

  const dayStart = startOfDay(date);
  const dayEnd = endOfDay(date);

  const people = await prisma.person.findMany({
    include: {
      dailyOverrides: {
        where: {
          date: {
            gte: dayStart,
            lte: dayEnd
          }
        }
      },
      payments: {
        where: {
          remainingDays: {
            gt: 0
          }
        },
        orderBy: { createdAt: "asc" }
      }
    }
  });

  for (const person of people) {
    const override = person.dailyOverrides[0];
    const eligible = person.status === PersonStatus.active && !isWithinLeave(person.leaveStart, person.leaveEnd, date) && !override?.isAbsent;

    if (!eligible) {
      continue;
    }

    const needsLunch = person.takesLunch;
    const needsMandazi = person.takesMandazi;

    for (const payment of person.payments) {
      const coversLunch = payment.mealType === PaymentMealType.lunch || payment.mealType === PaymentMealType.both;
      const coversMandazi = payment.mealType === PaymentMealType.mandazi || payment.mealType === PaymentMealType.both;
      const shouldConsume = (needsLunch && coversLunch) || (needsMandazi && coversMandazi);

      if (!shouldConsume || payment.remainingDays <= 0) {
        continue;
      }

      await prisma.payment.update({
        where: { id: payment.id },
        data: { remainingDays: { decrement: 1 } }
      });
    }
  }

  await prisma.activityLog.create({
    data: {
      type: marker,
      description: `Processed payment usage for ${format(date, "EEEE, MMM d")}`
    }
  });
}

export async function getPaymentOverview() {
  const [people, awaitingPayments] = await Promise.all([
    prisma.person.findMany({
      include: {
        payments: {
          orderBy: { createdAt: "desc" }
        }
      },
      orderBy: { name: "asc" }
    }),
    prisma.awaitingPayment.findMany({
      orderBy: { createdAt: "desc" }
    })
  ]);

  const paid = people.filter((person) => person.payments.some((payment) => payment.remainingDays > 0));
  const unpaid = people.filter((person) => !person.payments.some((payment) => payment.remainingDays > 0));

  return {
    paid,
    unpaid,
    awaitingPayments
  };
}

export async function getHistoryData() {
  const [payments, awaitingPayments, activity] = await Promise.all([
    prisma.payment.findMany({
      include: { person: true },
      orderBy: { createdAt: "desc" }
    }),
    prisma.awaitingPayment.findMany({
      orderBy: { createdAt: "desc" }
    }),
    prisma.activityLog.findMany({
      orderBy: { createdAt: "desc" },
      take: 50
    })
  ]);

  return {
    payments,
    awaitingPayments,
    activity
  };
}

export async function getHistoryIndexData() {
  const [payments, awaitingPayments, activity] = await Promise.all([
    prisma.payment.findMany({
      select: {
        id: true,
        createdAt: true
      },
      orderBy: { createdAt: "desc" }
    }),
    prisma.awaitingPayment.findMany({
      select: {
        id: true,
        createdAt: true
      },
      orderBy: { createdAt: "desc" }
    }),
    prisma.activityLog.findMany({
      select: {
        id: true,
        createdAt: true
      },
      where: {
        type: {
          not: {
            startsWith: "delivery_processed:"
          }
        }
      },
      orderBy: { createdAt: "desc" }
    })
  ]);

  const dateMap = new Map<
    string,
    {
      dateKey: string;
      paidCount: number;
      awaitingCount: number;
      activityCount: number;
      latestAt: Date;
    }
  >();

  function ensureEntry(date: Date) {
    const dateKey = toDateKey(date);
    const existing = dateMap.get(dateKey);

    if (existing) {
      if (date > existing.latestAt) {
        existing.latestAt = date;
      }
      return existing;
    }

    const entry = {
      dateKey,
      paidCount: 0,
      awaitingCount: 0,
      activityCount: 0,
      latestAt: date
    };

    dateMap.set(dateKey, entry);
    return entry;
  }

  for (const payment of payments) {
    ensureEntry(payment.createdAt).paidCount += 1;
  }

  for (const awaiting of awaitingPayments) {
    ensureEntry(awaiting.createdAt).awaitingCount += 1;
  }

  for (const item of activity) {
    ensureEntry(item.createdAt).activityCount += 1;
  }

  return [...dateMap.values()].sort((a, b) => b.latestAt.getTime() - a.latestAt.getTime());
}

export async function getHistoryForDate(dateKey: string) {
  const date = parseDateKey(dateKey);

  if (!date) {
    return null;
  }

  const dayStart = startOfDay(date);
  const dayEnd = endOfDay(date);

  const [payments, awaitingPayments, activity] = await Promise.all([
    prisma.payment.findMany({
      where: {
        createdAt: {
          gte: dayStart,
          lte: dayEnd
        }
      },
      include: { person: true },
      orderBy: { createdAt: "desc" }
    }),
    prisma.awaitingPayment.findMany({
      where: {
        createdAt: {
          gte: dayStart,
          lte: dayEnd
        }
      },
      orderBy: { createdAt: "desc" }
    }),
    prisma.activityLog.findMany({
      where: {
        type: {
          not: {
            startsWith: "delivery_processed:"
          }
        },
        createdAt: {
          gte: dayStart,
          lte: dayEnd
        }
      },
      orderBy: { createdAt: "desc" }
    })
  ]);

  return {
    date,
    dateKey,
    payments,
    awaitingPayments,
    activity
  };
}

export async function createReplacementLog(item: ReplacementItem, name: string, quantity: number) {
  const replacement = await prisma.replacement.create({
    data: {
      date: startOfDay(new Date()),
      item,
      name,
      quantity
    }
  });

  await prisma.activityLog.create({
    data: {
      type: "replacement_added",
      description: `Replacement added for ${name}: ${quantity} ${item}`
    }
  });

  return replacement;
}
