import { FastifyInstance } from "fastify";
import prisma from "../../config/database";
import { requireAuth } from "../../middleware/auth";
import { success } from "../../utils/response";
import { localTimeStr } from "../../utils/date";
import { toCzk } from "../../services/exchange-rates";
import { resolveCell } from "../../services/plan.service";

export default async function dashboardRoutes(
  fastify: FastifyInstance,
): Promise<void> {
  fastify.get("/", { preHandler: requireAuth }, async (request, reply) => {
    const now = new Date();
    // shift_date is @db.Date: Prisma truncates a filter Date to its UTC date
    // part, so these boundaries MUST be UTC-midnight instants of the LOCAL
    // (Prague) calendar day. A local-midnight Date (new Date(y, m, d)) is
    // 22:00/23:00 UTC of the PREVIOUS day and silently shifts the window to
    // [yesterday, today) — the "Docházka dnes" card then matches zero rows.
    const todayStart = new Date(
      Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()),
    );
    const todayEnd = new Date(
      Date.UTC(now.getFullYear(), now.getMonth(), now.getDate() + 1),
    );
    const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
    const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 1);
    const authData = request.authData!;
    const userId = authData.userId;
    const perms = authData.permissions;
    const has = (p: string) => perms.includes(p);

    const result: Record<string, unknown> = {};

    // My shift — always available for authenticated users with attendance.record
    if (has("attendance.record")) {
      const myShift = await prisma.attendance.findFirst({
        where: {
          user_id: userId,
          arrival_time: { not: null },
          departure_time: null,
        },
        orderBy: { created_at: "desc" },
      });
      result.my_shift = { has_ongoing: myShift !== null };
    }

    // Today's work plan (personal) — powers the "Vaše dnešní zařazení" card.
    // resolveCell returns 0–3 records (override-beats-range layering); an empty
    // array means the user has plan access but nothing is scheduled today. Each
    // record's category key is enriched with its label + colour so the widget
    // needs no extra query. todayStr uses local (Europe/Prague) calendar date —
    // the plan's @db.Date day.
    if (has("attendance.record") || has("attendance.manage")) {
      const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
      const cells = await resolveCell(userId, todayStr);
      // Resolve all category labels in one query (was a per-cell N+1) then map.
      const categoryKeys = [...new Set(cells.map((c) => c.category))];
      const categories = categoryKeys.length
        ? await prisma.plan_categories.findMany({
            where: { key: { in: categoryKeys } },
            select: { key: true, label: true, color: true },
          })
        : [];
      const categoryMap = new Map(categories.map((c) => [c.key, c]));
      result.today_plan = cells.map((cell) => {
        const cat = categoryMap.get(cell.category);
        return {
          ...cell,
          category_label: cat?.label ?? cell.category,
          category_color: cat?.color ?? null,
        };
      });
    }

    // Attendance admin — only for attendance.manage
    if (has("attendance.manage")) {
      const [todayAttendance, onLeaveToday, activeUsers] = await Promise.all([
        prisma.attendance.findMany({
          where: {
            shift_date: { gte: todayStart, lt: todayEnd },
            OR: [{ leave_type: null }, { leave_type: "work" }],
          },
          include: {
            users: { select: { id: true, first_name: true, last_name: true } },
          },
          orderBy: { arrival_time: "asc" },
        }),
        prisma.attendance.findMany({
          where: {
            shift_date: { gte: todayStart, lt: todayEnd },
            leave_type: { in: ["vacation", "sick", "unpaid"] },
          },
          include: {
            users: { select: { id: true, first_name: true, last_name: true } },
          },
        }),
        prisma.users.findMany({
          where: { is_active: true },
          select: { id: true, first_name: true, last_name: true },
          orderBy: [{ last_name: "asc" }, { first_name: "asc" }],
        }),
      ]);
      const usersCount = activeUsers.length;

      const userAttendanceMap = new Map<number, (typeof todayAttendance)[0]>();
      for (const a of todayAttendance) {
        const existing = userAttendanceMap.get(a.users.id);
        if (
          !existing ||
          (a.arrival_time &&
            existing.arrival_time &&
            a.arrival_time > existing.arrival_time)
        ) {
          userAttendanceMap.set(a.users.id, a);
        }
      }

      let presentCount = 0;
      const attendanceUsers: Array<{
        user_id: number;
        name: string;
        initials: string;
        status: string;
        arrived_at: string | null;
        leave_type?: string;
      }> = [];

      for (const a of userAttendanceMap.values()) {
        const user = a.users;
        const firstInitial = user.first_name?.charAt(0) ?? "";
        const lastInitial = user.last_name?.charAt(0) ?? "";
        let status = "out";
        if (a.arrival_time) {
          if (a.departure_time) status = "out";
          else if (a.break_start && !a.break_end) status = "away";
          else {
            status = "in";
            presentCount++;
          }
        }
        attendanceUsers.push({
          user_id: user.id,
          name: `${user.first_name} ${user.last_name}`,
          initials: `${firstInitial}${lastInitial}`.toUpperCase(),
          status,
          arrived_at: a.arrival_time ? localTimeStr(a.arrival_time) : null,
        });
      }

      const leaveUserIds = new Set<number>();
      for (const a of onLeaveToday) {
        if (leaveUserIds.has(a.users.id)) continue;
        leaveUserIds.add(a.users.id);
        const user = a.users;
        attendanceUsers.push({
          user_id: user.id,
          name: `${user.first_name} ${user.last_name}`,
          initials:
            `${user.first_name?.charAt(0) ?? ""}${user.last_name?.charAt(0) ?? ""}`.toUpperCase(),
          status: "leave",
          arrived_at: null,
          leave_type: (a.leave_type as string) || "vacation",
        });
      }

      // Show every active employee — those with no attendance record today
      // appear as "absent" so the card reflects the whole team, not just those
      // who clocked in or are on leave.
      const recordedUserIds = new Set<number>([
        ...userAttendanceMap.keys(),
        ...leaveUserIds,
      ]);
      for (const user of activeUsers) {
        if (recordedUserIds.has(user.id)) continue;
        attendanceUsers.push({
          user_id: user.id,
          name: `${user.first_name} ${user.last_name}`,
          initials:
            `${user.first_name?.charAt(0) ?? ""}${user.last_name?.charAt(0) ?? ""}`.toUpperCase(),
          status: "absent",
          arrived_at: null,
        });
      }

      result.attendance = {
        present_today: presentCount,
        total_active: usersCount,
        on_leave: leaveUserIds.size,
        users: attendanceUsers,
      };
      result.users_count = usersCount;
    }

    // Offers — only for offers.view
    if (has("offers.view")) {
      const [openCount, convertedCount, expiredCount, createdThisMonth] =
        await Promise.all([
          prisma.quotations.count({ where: { status: "active" } }),
          prisma.quotations.count({ where: { status: "ordered" } }),
          prisma.quotations.count({ where: { status: "invalidated" } }),
          prisma.quotations.count({
            where: {
              // Drafts aren't real offers — keep them out of the count.
              status: { not: "draft" },
              created_at: { gte: monthStart, lt: monthEnd },
            },
          }),
        ]);
      result.offers = {
        open_count: openCount,
        converted_count: convertedCount,
        expired_count: expiredCount,
        created_this_month: createdThisMonth,
      };
    }

    // Projects — only for projects.view
    if (has("projects.view")) {
      const [activeCount, activeList] = await Promise.all([
        prisma.projects.count({ where: { status: "aktivni" } }),
        prisma.projects.findMany({
          where: { status: "aktivni" },
          include: { customers: { select: { name: true } } },
          orderBy: { created_at: "desc" },
          take: 5,
        }),
      ]);
      result.active_projects = activeCount;
      result.projects = {
        active_projects: activeList.map((p) => ({
          id: p.id,
          name: p.name ?? "",
          customer_name: p.customers?.name ?? null,
        })),
      };
    }

    // Invoices — only for invoices.view
    if (has("invoices.view")) {
      // $queryRaw template literal interpolation with Date objects fails on
      // MySQL when Date.toJSON is overridden — pass explicit date strings instead.
      const monthStartStr = `${monthStart.getFullYear()}-${String(monthStart.getMonth() + 1).padStart(2, "0")}-01`;
      const monthEndStr = `${monthEnd.getFullYear()}-${String(monthEnd.getMonth() + 1).padStart(2, "0")}-01`;

      const [unpaidCount, revenueAgg] = await Promise.all([
        prisma.invoices.count({ where: { status: "issued" } }),
        prisma.$queryRaw<
          Array<{ currency: string | null; total: string | number | null }>
        >`
          SELECT i.currency, SUM(ii.quantity * ii.unit_price) as total
          FROM invoices i
          JOIN invoice_items ii ON i.id = ii.invoice_id
          WHERE i.issue_date >= ${monthStartStr} AND i.issue_date < ${monthEndStr}
            AND i.status <> 'draft'
          GROUP BY i.currency
        `,
      ]);

      const revenueByCurrency: Record<string, number> = {};
      for (const row of revenueAgg) {
        const currency = row.currency || "CZK";
        const amount = Number(row.total) || 0;
        revenueByCurrency[currency] =
          (revenueByCurrency[currency] || 0) + amount;
      }

      const revenueConversions = await Promise.all(
        Object.entries(revenueByCurrency).map(async ([currency, amount]) => ({
          amount: Math.round(amount * 100) / 100,
          currency,
          czk: await toCzk(Math.round(amount * 100) / 100, currency),
        })),
      );

      result.invoices = {
        revenue_this_month: revenueConversions.map(({ amount, currency }) => ({
          amount,
          currency,
        })),
        unpaid_count: unpaidCount,
        revenue_czk:
          Math.round(
            revenueConversions.reduce((sum, r) => sum + r.czk, 0) * 100,
          ) / 100,
      };
    }

    // Orders — only for orders.view
    if (has("orders.view")) {
      result.pending_orders = await prisma.orders.count({
        where: { status: "prijata" },
      });
    }

    // Leave pending — only for attendance.approve
    if (has("attendance.approve")) {
      const count = await prisma.leave_requests.count({
        where: { status: "pending" },
      });
      result.leave_pending = { count };
    }

    // Recent activity — only for settings.audit (admin)
    if (has("settings.audit")) {
      const logs = await prisma.audit_logs.findMany({
        orderBy: { created_at: "desc" },
        take: 8,
        where: { action: { in: ["create", "update", "delete", "login"] } },
        select: {
          id: true,
          action: true,
          entity_type: true,
          description: true,
          username: true,
          created_at: true,
        },
      });
      result.recent_activity = logs.map((log) => ({
        id: log.id,
        action: log.action,
        entity_type: log.entity_type ?? "",
        description: log.description ?? "",
        username: log.username ?? null,
        created_at: log.created_at,
      }));
    }

    return success(reply, result);
  });
}
