import { redirect } from "next/navigation";

import { DashboardShell } from "@/components/dashboard/dashboard-shell";
import { getCurrentAccountingPeriod } from "@/lib/accounting-periods";
import { getAppSettings, serializeAppSettings } from "@/lib/app-settings";
import { getRollbackMetadata } from "@/lib/audit-log";
import type {
  DashboardAccountingPeriod,
  DashboardAuditLog,
  DashboardDonation,
  DashboardManagedUser,
  DashboardSettings,
  DashboardViewer,
  DashboardWorkingGroup
} from "@/lib/dashboard-types";
import { canManageUsers, normalizeApprovalPermissions } from "@/lib/domain";
import prisma from "@/lib/prisma";
import { buildRecurringOccurrences, getExpensePeriodAmount } from "@/lib/recurring-expenses";
import { getCurrentViewer } from "@/lib/session";

export const dynamic = "force-dynamic";

export default async function DashboardPage() {
  const viewer = await getCurrentViewer();

  if (!viewer) {
    redirect("/login");
  }

  const [currentPeriod, appSettings] = await Promise.all([getCurrentAccountingPeriod(), getAppSettings()]);

  if (!currentPeriod) {
    throw new Error("Kein Abrechnungszeitraum gefunden.");
  }

  const accountingPeriods = await prisma.accountingPeriod.findMany({
    orderBy: {
      startsAt: "desc"
    }
  });

  const workingGroups = await prisma.workingGroup.findMany({
    include: {
      members: {
        select: {
          id: true,
          name: true,
          username: true,
          role: true
        }
      },
      budgets: {
        where: {
          periodId: currentPeriod.id
        },
        orderBy: {
          name: "asc"
        },
        include: {
          expenses: {
            orderBy: {
              createdAt: "desc"
            },
            include: {
              creator: {
                select: {
                  id: true,
                  username: true
                }
              },
              approvals: {
                orderBy: {
                  timestamp: "asc"
                },
                include: {
                  user: {
                    select: {
                      id: true,
                      username: true
                    }
                  }
                }
              },
              documents: {
                orderBy: {
                  createdAt: "asc"
                },
                include: {
                  uploadedBy: {
                    select: {
                      id: true,
                      username: true
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    orderBy: {
      name: "asc"
    }
  });

  const managedUsers = canManageUsers(viewer.role)
    ? await prisma.user.findMany({
        include: {
          workingGroup: {
            select: {
              name: true
            }
          },
          _count: {
            select: {
              approvals: true,
              createdExpenses: true
            }
          }
        },
        orderBy: [{ role: "asc" }, { username: "asc" }]
      })
    : [];

  const auditLogs = canManageUsers(viewer.role)
    ? await prisma.auditLog.findMany({
        orderBy: {
          createdAt: "desc"
        },
        take: 120,
        include: {
          actor: {
            select: {
              id: true,
              name: true,
              username: true,
              role: true
            }
          }
        }
      })
    : [];

  const periodCutoffs = await prisma.$queryRaw<
    { id: string; name: string; date: Date | null; period_id: string }[]
  >`
    SELECT id, name, date, period_id
    FROM period_cutoffs
    ORDER BY date ASC NULLS LAST, created_at ASC
  `;
  const expenseCutoffs = await prisma.$queryRaw<
    { id: string; cutoff_id: string | null; cutoff_phase: "PRE" | "POST" }[]
  >`SELECT id, cutoff_id, cutoff_phase FROM expenses WHERE period_id = ${currentPeriod.id}`;
  const donationRows = await prisma.$queryRaw<
    {
      id: string;
      title: string;
      description: string | null;
      amount: unknown;
      donated_at: Date;
      period_id: string;
      expense_id: string | null;
      created_at: Date;
      creator_id: string;
      creator_name: string;
      working_group_id: string | null;
      working_group_name: string | null;
      expense_title: string | null;
    }[]
  >`
    SELECT d.id, d.title, d.description, d.amount, d.donated_at, d.period_id, d.expense_id, d.created_at,
           u.id AS creator_id, u.username AS creator_name,
           wg.id AS working_group_id, wg.name AS working_group_name, e.title AS expense_title
    FROM donations d
    JOIN users u ON u.id = d.creator_id
    LEFT JOIN expenses e ON e.id = d.expense_id
    LEFT JOIN working_groups wg ON wg.id = e.ag_id
    WHERE d.period_id = ${currentPeriod.id}
    ORDER BY d.donated_at DESC
  `;
  const periodCutoffsByPeriodId = new Map<string, typeof periodCutoffs>();
  for (const cutoff of periodCutoffs) {
    periodCutoffsByPeriodId.set(cutoff.period_id, [...(periodCutoffsByPeriodId.get(cutoff.period_id) ?? []), cutoff]);
  }
  const primaryCutoffByPeriodId = new Map(
    [...periodCutoffsByPeriodId.entries()].map(([periodId, cutoffs]) => [periodId, cutoffs[0]])
  );
  const expenseCutoffById = new Map(expenseCutoffs.map((expense) => [expense.id, expense]));
  const donationsByExpenseId = new Map<string, number>();
  const donationRowsByExpenseId = new Map<string, typeof donationRows>();
  for (const donation of donationRows) {
    if (donation.expense_id) {
      donationsByExpenseId.set(
        donation.expense_id,
        (donationsByExpenseId.get(donation.expense_id) ?? 0) + Number(donation.amount)
      );
      donationRowsByExpenseId.set(donation.expense_id, [
        ...(donationRowsByExpenseId.get(donation.expense_id) ?? []),
        donation
      ]);
    }
  }

  const serializedViewer: DashboardViewer = {
    id: viewer.id,
    name: viewer.username,
    username: viewer.username,
    role: viewer.role,
    workingGroupId: viewer.workingGroupId,
    approvalPermissions: normalizeApprovalPermissions(viewer.role, viewer.approvalPermissions, viewer.approvalPreference)
  };

  const serializedGroups: DashboardWorkingGroup[] = workingGroups.map((workingGroup) => ({
    id: workingGroup.id,
    name: workingGroup.name,
    totalBudget: workingGroup.budgets.reduce((sum, budget) => sum + Number(budget.totalBudget), 0),
    members: workingGroup.members.map((member) => ({
      id: member.id,
      name: member.username,
      username: member.username,
      role: member.role
    })),
    budgets: workingGroup.budgets.map((budget) => ({
      id: budget.id,
      name: budget.name,
      totalBudget: Number(budget.totalBudget),
      releasedAmount: Number(budget.releasedAmount),
      colorCode: budget.colorCode,
      periodId: budget.periodId,
      expenses: budget.expenses.map((expense) => {
        const amount = Number(expense.amount);
        const recurrenceStartAt =
          expense.recurrence === "MONTHLY"
            ? (expense.recurrenceStartAt ?? expense.createdAt).toISOString()
            : null;
        const occurrences =
          expense.recurrence === "MONTHLY" && recurrenceStartAt
            ? buildRecurringOccurrences({
                expenseId: expense.id,
                amount,
                recurrenceStartAt,
                periodStartsAt: currentPeriod.startsAt,
                periodEndsAt: currentPeriod.endsAt
              })
            : [];

        return {
          id: expense.id,
          title: expense.title,
          description: expense.description,
          amount,
          periodAmount: getExpensePeriodAmount(amount, expense.recurrence, occurrences.length),
          occurrenceCount: expense.recurrence === "MONTHLY" ? occurrences.length : 1,
          occurrences,
          budgetId: expense.budgetId,
          periodId: expense.periodId,
          approvalStatus: expense.approvalStatus,
          recurrence: expense.recurrence,
          recurrenceStartAt,
          cutoffId: expenseCutoffById.get(expense.id)?.cutoff_id ?? primaryCutoffByPeriodId.get(expense.periodId)?.id ?? null,
          cutoffPhase: expenseCutoffById.get(expense.id)?.cutoff_phase ?? "PRE",
          donationAmount: donationsByExpenseId.get(expense.id) ?? 0,
          donations: (donationRowsByExpenseId.get(expense.id) ?? []).map((donation) => ({
            id: donation.id,
            title: donation.title,
            description: donation.description,
            amount: Number(donation.amount),
            donatedAt: donation.donated_at.toISOString()
          })),
          netPeriodAmount: Math.max(
            0,
            getExpensePeriodAmount(amount, expense.recurrence, occurrences.length) -
              (donationsByExpenseId.get(expense.id) ?? 0)
          ),
          paidAt: expense.paidAt?.toISOString() ?? null,
          documentedAt: expense.documentedAt?.toISOString() ?? null,
          documents: expense.documents.map((document) => ({
            id: document.id,
            invoiceDate: document.invoiceDate.toISOString(),
            proofUrl: document.proofUrl,
            storedFileName: document.storedFileName,
            originalFileName: document.originalFileName,
            mimeType: document.mimeType,
            size: document.size,
            createdAt: document.createdAt.toISOString(),
            uploadedBy: {
              id: document.uploadedBy.id,
              name: document.uploadedBy.username
            }
          })),
          createdAt: expense.createdAt.toISOString(),
          creator: {
            id: expense.creator.id,
            name: expense.creator.username
          },
          approvals: expense.approvals.map((approval) => ({
            id: approval.id,
            approvalType: approval.approvalType,
            timestamp: approval.timestamp.toISOString(),
            user: {
              id: approval.user.id,
              name: approval.user.username
            }
          }))
        };
      })
    }))
  }));

  const serializedUsers: DashboardManagedUser[] = managedUsers.map((user) => ({
    id: user.id,
    name: user.username,
    username: user.username,
    role: user.role,
    workingGroupId: user.workingGroupId,
    workingGroupName: user.workingGroup?.name ?? null,
    approvalPermissions: normalizeApprovalPermissions(user.role, user.approvalPermissions, user.approvalPreference),
    createdExpensesCount: user._count.createdExpenses,
    approvalsCount: user._count.approvals
  }));

  const serializedPeriods: DashboardAccountingPeriod[] = accountingPeriods.map((period) => ({
    cutoffs: (periodCutoffsByPeriodId.get(period.id) ?? []).map((cutoff) => ({
      id: cutoff.id,
      name: cutoff.name,
      date: cutoff.date?.toISOString() ?? null,
      periodId: cutoff.period_id
    })),
    id: period.id,
    name: period.name,
    startsAt: period.startsAt.toISOString(),
    endsAt: period.endsAt.toISOString(),
    isCurrent: period.isCurrent,
    cutoffName: primaryCutoffByPeriodId.get(period.id)?.name ?? "Open Air",
    cutoffDate: primaryCutoffByPeriodId.get(period.id)?.date?.toISOString() ?? null
  }));

  const serializedDonations: DashboardDonation[] = donationRows.map((donation) => ({
    id: donation.id,
    title: donation.title,
    description: donation.description,
    amount: Number(donation.amount),
    donatedAt: donation.donated_at.toISOString(),
    periodId: donation.period_id,
    expenseId: donation.expense_id,
    workingGroupId: donation.working_group_id,
    workingGroupName: donation.working_group_name,
    expenseTitle: donation.expense_title,
    createdAt: donation.created_at.toISOString(),
    creator: {
      id: donation.creator_id,
      name: donation.creator_name
    }
  }));

  const serializedSettings: DashboardSettings = serializeAppSettings(appSettings);

  const serializedAuditLogs: DashboardAuditLog[] = auditLogs.map((entry) => ({
    id: entry.id,
    action: entry.action,
    entityType: entry.entityType,
    entityId: entry.entityId,
    entityLabel: entry.entityLabel,
    summary: entry.summary,
    canRestore: Boolean(getRollbackMetadata(entry.metadata)),
    createdAt: entry.createdAt.toISOString(),
    actor: entry.actor
      ? {
          id: entry.actor.id,
          name: entry.actor.username,
          username: entry.actor.username,
          role: entry.actor.role
        }
      : null
  }));

  return (
    <DashboardShell
      viewer={serializedViewer}
      workingGroups={serializedGroups}
      managedUsers={serializedUsers}
      auditLogs={serializedAuditLogs}
      accountingPeriods={serializedPeriods}
      currentPeriodId={currentPeriod.id}
      settings={serializedSettings}
      donations={serializedDonations}
    />
  );
}
