import type { Prisma } from "@prisma/client";
import {
  calculatePartnerBalances,
  calculateProfitLoss,
  createSettlementInstructions,
  type BalanceInput,
  type PartnerInput
} from "./calculations";
import { operatingCostPhases } from "./finance";
import { isPartnerPersonalSource, isProjectFundSource } from "./funding";
import { effectiveBillStatus, startOfDay } from "./recurringBills";

export const projectDetailInclude = {
  partners: { where: { archivedAt: null }, include: { partner: true }, orderBy: { createdAt: "asc" as const } },
  projectAccounts: { orderBy: [{ active: "desc" as const }, { createdAt: "asc" as const }] },
  categories: { orderBy: { name: "asc" as const } },
  documentCategories: { orderBy: { name: "asc" as const } },
  documents: {
    where: { status: { not: "ARCHIVED" as const } },
    include: {
      category: true,
      uploadedBy: true,
      relatedTenant: true,
      relatedUnit: true,
      relatedPartner: { include: { partner: true } },
      relatedExpense: { include: { category: true } },
      relatedRentPayment: { include: { tenant: true, unit: true } },
      files: { orderBy: { createdAt: "asc" as const } }
    },
    orderBy: { createdAt: "desc" as const }
  },
  recurringBills: {
    where: { status: { not: "ARCHIVED" as const } },
    include: {
      category: true,
      relatedDocument: true,
      defaultPaidBy: { include: { partner: true } },
      occurrences: {
        include: {
          category: true,
          relatedExpense: true,
          paidBy: { include: { partner: true } }
        },
        orderBy: { dueDate: "asc" as const }
      }
    },
    orderBy: { nextDueDate: "asc" as const }
  },
  billOccurrences: {
    where: { status: { not: "ARCHIVED" as const } },
    include: {
      recurringBill: {
        include: {
          category: true,
          relatedDocument: true,
          defaultPaidBy: { include: { partner: true } },
          occurrences: true
        }
      },
      category: true,
      relatedExpense: true,
      paidBy: { include: { partner: true } }
    },
    orderBy: { dueDate: "asc" as const }
  },
  expenses: {
    where: { archived: false },
    include: {
      category: true,
      worker: true,
      paidBy: { include: { partner: true } },
      fundingPartner: { include: { partner: true } },
      projectAccount: true,
      contributionCreditPartner: { include: { partner: true } },
      createdBy: true,
      shares: { include: { projectPartner: { include: { partner: true } } } },
      receiptFiles: { where: { archived: false }, orderBy: { createdAt: "desc" as const } }
    },
    orderBy: { date: "desc" as const }
  },
  workers: { where: { archivedAt: null }, orderBy: [{ isActive: "desc" as const }, { name: "asc" as const }] },
  units: { where: { archived: false }, orderBy: { createdAt: "desc" as const } },
  tenants: { where: { archived: false }, include: { unit: true }, orderBy: { createdAt: "desc" as const } },
  contributions: { where: { archived: false }, include: { partner: { include: { partner: true } }, projectAccount: true }, orderBy: { date: "desc" as const } },
  securityDeposits: { where: { archived: false }, include: { tenant: true, unit: true }, orderBy: { createdAt: "desc" as const } },
  rentPayments: {
    where: { archived: false },
    include: { tenant: true, unit: true },
    orderBy: { month: "desc" as const }
  },
  incomes: { where: { archived: false }, include: { category: true }, orderBy: { date: "desc" as const } },
  operatingExpenses: { where: { archived: false }, include: { category: true }, orderBy: { date: "desc" as const } }
} satisfies Prisma.ProjectInclude;

export type ProjectDetail = Prisma.ProjectGetPayload<{ include: typeof projectDetailInclude }>;

export function toNumber(value: unknown) {
  if (value === null || value === undefined) return 0;
  return Number(value);
}

export function partnerInputs(project: ProjectDetail): PartnerInput[] {
  return project.partners.map((partner) => ({
    id: partner.id,
    name: partner.partner.name,
    ownershipPercentage: toNumber(partner.ownershipPercentage),
    active: partner.active
  }));
}

export function expenseInputs(project: ProjectDetail): BalanceInput[] {
  return [
    ...project.expenses.map((expense) => ({
      paidByPartnerId: expense.createsContributionCredit
        ? (expense.contributionCreditPartnerId ?? expense.fundingPartnerId ?? expense.paidById)
        : null,
      handledByPartnerId: expense.paidById,
      finalCost: expense.createsContributionCredit
        ? toNumber(expense.contributionCreditAmount ?? expense.finalCost)
        : toNumber(expense.finalCost),
      creditType: "OUT_OF_POCKET" as const,
      projectFundPayment: isProjectFundSource(expense.fundingSourceType),
      shares: expense.shares.map((share) => ({
        partnerId: share.projectPartnerId,
        shareAmount: toNumber(share.shareAmount),
        sharePercentage: share.sharePercentage === null ? undefined : toNumber(share.sharePercentage)
      }))
    })),
    ...project.contributions.map((contribution) => ({
      paidByPartnerId: contribution.partnerId,
      finalCost: toNumber(contribution.amount),
      creditType: "CAPITAL_CONTRIBUTION" as const,
      shares: []
    }))
  ];
}

export function buildProjectMetrics(project: ProjectDetail) {
  const partners = partnerInputs(project);
  const expenses = expenseInputs(project);
  const balances = calculatePartnerBalances(partners, expenses);
  const settlements = createSettlementInstructions(balances);
  const purchaseCosts = project.expenses
    .filter((expense) => expense.phase === "PRE_PURCHASE" || expense.phase === "ACQUISITION_CLOSING" || (expense.phase === "OTHER" && expense.category?.type === "PURCHASE_COST"))
    .reduce((sum, expense) => sum + toNumber(expense.finalCost), 0);
  const prePurchaseCosts = project.expenses
    .filter((expense) => expense.phase === "PRE_PURCHASE")
    .reduce((sum, expense) => sum + toNumber(expense.finalCost), 0);
  const acquisitionCosts = project.expenses
    .filter((expense) => expense.phase === "ACQUISITION_CLOSING" || (expense.phase === "OTHER" && expense.category?.type === "PURCHASE_COST"))
    .reduce((sum, expense) => sum + toNumber(expense.finalCost), 0);
  const renovationCosts = project.expenses
    .filter((expense) => expense.phase === "RENOVATION_CAPITAL_IMPROVEMENT" || (expense.phase === "OTHER" && expense.category?.type === "RENOVATION_EXPENSE"))
    .reduce((sum, expense) => sum + toNumber(expense.finalCost), 0);
  const operatingExpenseCost = project.expenses
    .filter((expense) => operatingCostPhases.includes(expense.phase))
    .reduce((sum, expense) => sum + toNumber(expense.finalCost), 0);
  const discounts = project.expenses.reduce((sum, expense) => sum + toNumber(expense.discount), 0);
  const allExpenseCost = project.expenses.reduce((sum, expense) => sum + toNumber(expense.finalCost), 0);
  const contributionTotal = project.contributions.reduce((sum, contribution) => sum + toNumber(contribution.amount), 0);
  const projectFundPayments = project.expenses
    .filter((expense) => isProjectFundSource(expense.fundingSourceType))
    .reduce((sum, expense) => sum + toNumber(expense.finalCost), 0);
  const outOfPocketPartnerPayments = project.expenses
    .filter((expense) => expense.createsContributionCredit && isPartnerPersonalSource(expense.fundingSourceType))
    .reduce((sum, expense) => sum + toNumber(expense.contributionCreditAmount ?? expense.finalCost), 0);
  const recordsNeedingFundingReview = project.expenses.filter((expense) => expense.fundingSourceType === "UNKNOWN").length;
  const trackedAccountBalances = project.projectAccounts
    .filter((account) => account.active && account.currentBalance !== null)
    .map((account) => toNumber(account.currentBalance));
  const inferredProjectFundBalance = contributionTotal - projectFundPayments;
  const projectFundBalance = trackedAccountBalances.length > 0
    ? trackedAccountBalances.reduce((sum, amount) => sum + amount, 0)
    : inferredProjectFundBalance;
  const now = new Date();
  const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
  const rentThisMonth = project.rentPayments.filter((payment) => {
    const month = payment.month.toISOString().slice(0, 7);
    return month === currentMonth;
  });
  const rentExpected = rentThisMonth.reduce((sum, payment) => sum + toNumber(payment.rentExpected), 0);
  const rentReceived = rentThisMonth.reduce((sum, payment) => sum + toNumber(payment.rentPaid), 0);
  const outstandingRent = project.rentPayments.reduce(
    (sum, payment) => sum + Math.max(0, toNumber(payment.balanceDue)),
    0
  );
  const income = [
    ...project.incomes.map((item) => toNumber(item.amount)),
    ...project.rentPayments.map((payment) =>
      toNumber(payment.rentPaid) + toNumber(payment.lateFee) + toNumber(payment.utilityCharge) + toNumber(payment.parkingFee) + toNumber(payment.otherCharge)
    ),
    ...project.securityDeposits.map((deposit) => toNumber(deposit.amountWithheld))
  ];
  const operatingExpenses = [
    ...project.operatingExpenses.map((item) => toNumber(item.amount)),
    ...project.expenses.filter((expense) => operatingCostPhases.includes(expense.phase)).map((expense) => toNumber(expense.finalCost))
  ];
  const profitLoss = calculateProfitLoss({ income, expenses: operatingExpenses, partners });
  const totalDepositsHeld = project.securityDeposits.reduce(
    (sum, deposit) => sum + Math.max(0, toNumber(deposit.amountReceived) - toNumber(deposit.amountReturned) - toNumber(deposit.amountWithheld)),
    0
  );
  const totalDepositsReturned = project.securityDeposits.reduce((sum, deposit) => sum + toNumber(deposit.amountReturned), 0);
  const totalDepositsWithheld = project.securityDeposits.reduce((sum, deposit) => sum + toNumber(deposit.amountWithheld), 0);
  const initialInvestment = prePurchaseCosts + acquisitionCosts + renovationCosts;
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  const soon = new Date(today);
  soon.setDate(soon.getDate() + 30);
  const totalDocuments = project.documents.length;
  const documentsNeedingReview = project.documents.filter((document) => document.status === "NEEDS_REVIEW" || document.reminderStatus === "OPEN").length;
  const expiringSoonDocuments = project.documents.filter((document) =>
    document.expirationDate !== null && document.expirationDate >= today && document.expirationDate <= soon
  ).length;
  const expiredDocuments = project.documents.filter((document) =>
    document.status === "EXPIRED" || (document.expirationDate !== null && document.expirationDate < today)
  ).length;
  const tenantLeaseDocuments = project.documents.filter((document) => document.category?.name.toLowerCase().includes("tenant") || document.category?.name.toLowerCase().includes("lease")).length;
  const insuranceDocuments = project.documents.filter((document) => document.category?.name.toLowerCase().includes("insurance")).length;
  const weekEnd = new Date(today);
  weekEnd.setDate(weekEnd.getDate() + 7);
  const monthStart = new Date(today.getFullYear(), today.getMonth(), 1);
  const monthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0);
  const activeBillOccurrences = project.billOccurrences.filter((occurrence) => {
    const status = effectiveBillStatus({
      dueDate: occurrence.dueDate,
      lastDateToPay: occurrence.lastDateToPay,
      status: occurrence.status,
      gracePeriodDays: occurrence.recurringBill.gracePeriodDays
    });
    return status !== "PAID" && status !== "SKIPPED" && status !== "ARCHIVED";
  });
  const billsDueThisWeek = activeBillOccurrences.filter((occurrence) => {
    const dueDate = startOfDay(occurrence.dueDate);
    return dueDate >= today && dueDate <= weekEnd;
  }).length;
  const billsDueThisMonth = activeBillOccurrences.filter((occurrence) => {
    const dueDate = startOfDay(occurrence.dueDate);
    return dueDate >= monthStart && dueDate <= monthEnd;
  }).length;
  const overdueBills = activeBillOccurrences.filter((occurrence) =>
    effectiveBillStatus({
      dueDate: occurrence.dueDate,
      lastDateToPay: occurrence.lastDateToPay,
      status: occurrence.status,
      gracePeriodDays: occurrence.recurringBill.gracePeriodDays
    }) === "OVERDUE"
  ).length;
  const autopayBills = project.recurringBills.filter((bill) => bill.autopay && bill.status !== "ARCHIVED").length;
  const expectedBillsThisMonth = activeBillOccurrences
    .filter((occurrence) => {
      const dueDate = startOfDay(occurrence.dueDate);
      return dueDate >= monthStart && dueDate <= monthEnd;
    })
    .reduce((sum, occurrence) => sum + toNumber(occurrence.amount), 0);

  return {
    partners,
    balances,
    settlements,
    prePurchaseCosts,
    acquisitionCosts,
    purchaseCosts,
    renovationCosts,
    operatingExpenseCost,
    discounts,
    allExpenseCost,
    contributionTotal,
    projectFundPayments,
    outOfPocketPartnerPayments,
    recordsNeedingFundingReview,
    projectFundBalance,
    initialInvestment,
    finalProjectCost: toNumber(project.purchasePrice) + initialInvestment,
    rentExpected,
    rentReceived,
    outstandingRent,
    totalDepositsHeld,
    totalDepositsReturned,
    totalDepositsWithheld,
    totalDocuments,
    documentsNeedingReview,
    expiringSoonDocuments,
    expiredDocuments,
    tenantLeaseDocuments,
    insuranceDocuments,
    billsDueThisWeek,
    billsDueThisMonth,
    overdueBills,
    autopayBills,
    expectedBillsThisMonth,
    profitLoss
  };
}

export function groupByCategory(project: ProjectDetail) {
  const totals = new Map<string, number>();
  for (const expense of project.expenses) {
    const name = expense.category?.name ?? "Uncategorized";
    totals.set(name, (totals.get(name) ?? 0) + toNumber(expense.finalCost));
  }
  return [...totals.entries()].map(([name, total]) => ({ name, total }));
}

export function groupByMonth(project: ProjectDetail) {
  const totals = new Map<string, number>();
  for (const expense of project.expenses) {
    const month = expense.date.toISOString().slice(0, 7);
    totals.set(month, (totals.get(month) ?? 0) + toNumber(expense.finalCost));
  }
  return [...totals.entries()].sort().map(([month, total]) => ({ month, total }));
}
