import type { Express } from "express";
import { createServer, type Server } from "http";
import { storage } from "./storage";
import { setupAuth as setupReplitAuth, isAuthenticated as isAuthenticatedReplit } from "./replitAuth";
import { setupLocalAuth, isAuthenticated as isAuthenticatedLocal } from "./localAuth";
import { db } from "./db";
import { guards, certifications, sites, shifts, shiftAssignments, users, insertShiftSchema, contractParameters, vehicles, serviceTypes, createMultiDayShiftSchema, insertCustomerSchema } from "@shared/schema";
import { eq, and, gte, lte, desc, asc, ne, sql } from "drizzle-orm";
import { differenceInDays, differenceInHours, differenceInMinutes, startOfWeek, endOfWeek, startOfMonth, endOfMonth, isWithinInterval, startOfDay, isSameDay, parseISO, format, isValid, addDays } from "date-fns";
import { z } from "zod";
import { fromZodError } from "zod-validation-error";

// Determina quale sistema auth usare basandosi sull'ambiente
const USE_LOCAL_AUTH = process.env.DOMAIN === "vt.alfacom.it" || !process.env.REPLIT_DOMAINS;

// Helper per estrarre user ID in modo compatibile
function getUserId(req: any): string {
  if (USE_LOCAL_AUTH) {
    return req.user?.id || "";
  } else {
    return req.user?.claims?.sub || "";
  }
}

// Usa il middleware auth appropriato
const isAuthenticated = USE_LOCAL_AUTH ? isAuthenticatedLocal : isAuthenticatedReplit;

export async function registerRoutes(app: Express): Promise<Server> {
  // Setup auth system appropriato
  if (USE_LOCAL_AUTH) {
    console.log("🔐 Usando Local Auth (vt.alfacom.it)");
    await setupLocalAuth(app);
  } else {
    console.log("🔐 Usando Replit OIDC Auth");
    await setupReplitAuth(app);
  }

  // ============= AUTH ROUTES =============
  app.get("/api/auth/user", isAuthenticated, async (req: any, res) => {
    try {
      const userId = getUserId(req);
      const user = await storage.getUser(userId);
      res.json(user);
    } catch (error) {
      console.error("Error fetching user:", error);
      res.status(500).json({ message: "Failed to fetch user" });
    }
  });

  // ============= USER MANAGEMENT ROUTES =============
  app.get("/api/users", isAuthenticated, async (req: any, res) => {
    try {
      const currentUserId = getUserId(req);
      const currentUser = await storage.getUser(currentUserId);
      
      // Only admins can view all users
      if (currentUser?.role !== "admin") {
        return res.status(403).json({ message: "Forbidden: Admin access required" });
      }
      
      const allUsers = await storage.getAllUsers();
      res.json(allUsers);
    } catch (error) {
      console.error("Error fetching users:", error);
      res.status(500).json({ message: "Failed to fetch users" });
    }
  });

  app.post("/api/users", isAuthenticated, async (req: any, res) => {
    try {
      const currentUserId = getUserId(req);
      const currentUser = await storage.getUser(currentUserId);
      
      // Only admins can create users
      if (currentUser?.role !== "admin") {
        return res.status(403).json({ message: "Forbidden: Admin access required" });
      }
      
      const { email, firstName, lastName, password, role } = req.body;
      
      if (!email || !firstName || !lastName || !password) {
        return res.status(400).json({ message: "Missing required fields" });
      }
      
      // Hash password
      const bcrypt = await import("bcrypt");
      const passwordHash = await bcrypt.hash(password, 10);
      
      // Generate UUID
      const crypto = await import("crypto");
      const userId = crypto.randomUUID();
      
      const newUser = await storage.upsertUser({
        id: userId,
        email,
        firstName,
        lastName,
        profileImageUrl: null,
        passwordHash,
      });
      
      // Set role if provided
      if (role) {
        await storage.updateUserRole(newUser.id, role);
      }
      
      res.json(newUser);
    } catch (error: any) {
      console.error("Error creating user:", error);
      if (error.code === '23505') { // Unique violation
        return res.status(409).json({ message: "Email già esistente" });
      }
      res.status(500).json({ message: "Failed to create user" });
    }
  });

  app.patch("/api/users/:id", isAuthenticated, async (req: any, res) => {
    try {
      const currentUserId = getUserId(req);
      const currentUser = await storage.getUser(currentUserId);
      
      // Only admins can update user roles
      if (currentUser?.role !== "admin") {
        return res.status(403).json({ message: "Forbidden: Admin access required" });
      }
      
      // Prevent admins from changing their own role
      if (req.params.id === currentUserId) {
        return res.status(403).json({ message: "Cannot change your own role" });
      }
      
      const { role } = req.body;
      if (!role || !["admin", "coordinator", "guard", "client"].includes(role)) {
        return res.status(400).json({ message: "Invalid role" });
      }
      
      const updated = await storage.updateUserRole(req.params.id, role);
      if (!updated) {
        return res.status(404).json({ message: "User not found" });
      }
      res.json(updated);
    } catch (error) {
      console.error("Error updating user role:", error);
      res.status(500).json({ message: "Failed to update user role" });
    }
  });

  app.put("/api/users/:id", isAuthenticated, async (req: any, res) => {
    try {
      const currentUserId = getUserId(req);
      const currentUser = await storage.getUser(currentUserId);
      
      // Only admins can update users
      if (currentUser?.role !== "admin") {
        return res.status(403).json({ message: "Forbidden: Admin access required" });
      }
      
      const { email, firstName, lastName, password, role } = req.body;
      const existingUser = await storage.getUser(req.params.id);
      
      if (!existingUser) {
        return res.status(404).json({ message: "User not found" });
      }
      
      // Prepare update data
      const updateData: any = {
        id: req.params.id,
        email: email || existingUser.email,
        firstName: firstName || existingUser.firstName,
        lastName: lastName || existingUser.lastName,
        profileImageUrl: existingUser.profileImageUrl,
      };
      
      // Hash new password if provided
      if (password) {
        const bcrypt = await import("bcrypt");
        updateData.passwordHash = await bcrypt.hash(password, 10);
      }
      
      const updated = await storage.upsertUser(updateData);
      
      // Update role if provided and not changing own role
      if (role && req.params.id !== currentUserId) {
        await storage.updateUserRole(req.params.id, role);
      }
      
      res.json(updated);
    } catch (error: any) {
      console.error("Error updating user:", error);
      if (error.code === '23505') {
        return res.status(409).json({ message: "Email già esistente" });
      }
      res.status(500).json({ message: "Failed to update user" });
    }
  });

  app.delete("/api/users/:id", isAuthenticated, async (req: any, res) => {
    try {
      const currentUserId = getUserId(req);
      const currentUser = await storage.getUser(currentUserId);
      
      // Only admins can delete users
      if (currentUser?.role !== "admin") {
        return res.status(403).json({ message: "Forbidden: Admin access required" });
      }
      
      // Prevent admins from deleting themselves
      if (req.params.id === currentUserId) {
        return res.status(403).json({ message: "Cannot delete your own account" });
      }
      
      await storage.deleteUser(req.params.id);
      res.json({ success: true });
    } catch (error) {
      console.error("Error deleting user:", error);
      res.status(500).json({ message: "Failed to delete user" });
    }
  });

  // ============= GUARD ROUTES =============
  app.get("/api/guards", isAuthenticated, async (req, res) => {
    try {
      const allGuards = await storage.getAllGuards();
      
      // Fetch related data for each guard
      const guardsWithDetails = await Promise.all(
        allGuards.map(async (guard) => {
          const certs = await storage.getCertificationsByGuard(guard.id);
          const user = guard.userId ? await storage.getUser(guard.userId) : undefined;
          
          // Update certification status based on expiry date
          for (const cert of certs) {
            const daysUntilExpiry = differenceInDays(new Date(cert.expiryDate), new Date());
            let newStatus: "valid" | "expiring_soon" | "expired" = "valid";
            
            if (daysUntilExpiry < 0) {
              newStatus = "expired";
            } else if (daysUntilExpiry <= 30) {
              newStatus = "expiring_soon";
            }
            
            if (cert.status !== newStatus) {
              await storage.updateCertificationStatus(cert.id, newStatus);
              cert.status = newStatus;
            }
          }
          
          return {
            ...guard,
            certifications: certs,
            user,
          };
        })
      );

      res.json(guardsWithDetails);
    } catch (error) {
      console.error("Error fetching guards:", error);
      res.status(500).json({ message: "Failed to fetch guards" });
    }
  });

  app.post("/api/guards", isAuthenticated, async (req, res) => {
    try {
      const guard = await storage.createGuard(req.body);
      res.json(guard);
    } catch (error) {
      console.error("Error creating guard:", error);
      res.status(500).json({ message: "Failed to create guard" });
    }
  });

  app.patch("/api/guards/:id", isAuthenticated, async (req, res) => {
    try {
      const updated = await storage.updateGuard(req.params.id, req.body);
      if (!updated) {
        return res.status(404).json({ message: "Guard not found" });
      }
      res.json(updated);
    } catch (error) {
      console.error("Error updating guard:", error);
      res.status(500).json({ message: "Failed to update guard" });
    }
  });

  app.delete("/api/guards/:id", isAuthenticated, async (req, res) => {
    try {
      const deleted = await storage.deleteGuard(req.params.id);
      if (!deleted) {
        return res.status(404).json({ message: "Guard not found" });
      }
      res.json({ success: true });
    } catch (error) {
      console.error("Error deleting guard:", error);
      res.status(500).json({ message: "Failed to delete guard" });
    }
  });

  // Get guards availability for general planning with time slot conflict detection
  app.get("/api/guards/availability", isAuthenticated, async (req, res) => {
    try {
      const { start, end, siteId, location } = req.query;
      
      if (!start || !end || !siteId || !location) {
        return res.status(400).json({ 
          message: "Missing required parameters: start, end, siteId, location" 
        });
      }
      
      const startDate = parseISO(start as string);
      const endDate = parseISO(end as string);
      
      if (!isValid(startDate) || !isValid(endDate)) {
        return res.status(400).json({ message: "Invalid date format for start or end" });
      }
      
      if (endDate <= startDate) {
        return res.status(400).json({ message: "End time must be after start time" });
      }
      
      const availability = await storage.getGuardsAvailability(
        siteId as string,
        location as string,
        startDate,
        endDate
      );
      
      res.json(availability);
    } catch (error) {
      console.error("Error fetching guards availability:", error);
      res.status(500).json({ message: "Failed to fetch guards availability" });
    }
  });
  
  // Get vehicles available for a location
  app.get("/api/vehicles/available", isAuthenticated, async (req, res) => {
    try {
      const { location } = req.query;
      
      if (!location) {
        return res.status(400).json({ message: "Missing required parameter: location" });
      }
      
      // Get all vehicles for this location with status 'available'
      const availableVehicles = await db
        .select()
        .from(vehicles)
        .where(
          and(
            eq(vehicles.location, location as any),
            eq(vehicles.status, "available")
          )
        )
        .orderBy(vehicles.licensePlate);
      
      res.json(availableVehicles);
    } catch (error) {
      console.error("Error fetching available vehicles:", error);
      res.status(500).json({ message: "Failed to fetch available vehicles" });
    }
  });

  // ============= VEHICLE ROUTES =============
  app.get("/api/vehicles", isAuthenticated, async (req, res) => {
    try {
      const vehicles = await storage.getAllVehicles();
      res.json(vehicles);
    } catch (error) {
      console.error("Error fetching vehicles:", error);
      res.status(500).json({ message: "Failed to fetch vehicles" });
    }
  });

  app.post("/api/vehicles", isAuthenticated, async (req, res) => {
    try {
      const vehicle = await storage.createVehicle(req.body);
      res.json(vehicle);
    } catch (error: any) {
      console.error("Error creating vehicle:", error);
      if (error.code === '23505') {
        return res.status(409).json({ message: "Targa già esistente" });
      }
      res.status(500).json({ message: "Failed to create vehicle" });
    }
  });

  app.patch("/api/vehicles/:id", isAuthenticated, async (req, res) => {
    try {
      const updated = await storage.updateVehicle(req.params.id, req.body);
      if (!updated) {
        return res.status(404).json({ message: "Vehicle not found" });
      }
      res.json(updated);
    } catch (error: any) {
      console.error("Error updating vehicle:", error);
      if (error.code === '23505') {
        return res.status(409).json({ message: "Targa già esistente" });
      }
      res.status(500).json({ message: "Failed to update vehicle" });
    }
  });

  app.delete("/api/vehicles/:id", isAuthenticated, async (req, res) => {
    try {
      const deleted = await storage.deleteVehicle(req.params.id);
      if (!deleted) {
        return res.status(404).json({ message: "Vehicle not found" });
      }
      res.json({ success: true });
    } catch (error) {
      console.error("Error deleting vehicle:", error);
      res.status(500).json({ message: "Failed to delete vehicle" });
    }
  });

  // ============= CONTRACT PARAMETERS ROUTES =============
  app.get("/api/contract-parameters", isAuthenticated, async (req: any, res) => {
    try {
      const currentUserId = getUserId(req);
      const currentUser = await storage.getUser(currentUserId);
      
      // Only admins and coordinators can view parameters
      if (currentUser?.role !== "admin" && currentUser?.role !== "coordinator") {
        return res.status(403).json({ message: "Forbidden: Admin or Coordinator access required" });
      }
      
      let params = await storage.getContractParameters();
      
      // Se non esistono parametri, creali con valori di default CCNL
      if (!params) {
        params = await storage.createContractParameters({
          contractType: "CCNL_VIGILANZA_2024",
        });
      }
      
      res.json(params);
    } catch (error) {
      console.error("Error fetching contract parameters:", error);
      res.status(500).json({ message: "Failed to fetch contract parameters" });
    }
  });

  app.put("/api/contract-parameters/:id", isAuthenticated, async (req: any, res) => {
    try {
      const currentUserId = getUserId(req);
      const currentUser = await storage.getUser(currentUserId);
      
      // Only admins can update parameters
      if (currentUser?.role !== "admin") {
        return res.status(403).json({ message: "Forbidden: Admin access required" });
      }
      
      // Validate request body with insert schema
      const { insertContractParametersSchema } = await import("@shared/schema");
      const validationResult = insertContractParametersSchema.partial().safeParse(req.body);
      
      if (!validationResult.success) {
        return res.status(400).json({ 
          message: "Invalid parameters data",
          errors: validationResult.error.errors 
        });
      }
      
      const updated = await storage.updateContractParameters(req.params.id, validationResult.data);
      if (!updated) {
        return res.status(404).json({ message: "Contract parameters not found" });
      }
      
      res.json(updated);
    } catch (error) {
      console.error("Error updating contract parameters:", error);
      res.status(500).json({ message: "Failed to update contract parameters" });
    }
  });

  // ============= CCNL SETTINGS ROUTES =============
  app.get("/api/ccnl-settings", isAuthenticated, async (req: any, res) => {
    try {
      const currentUserId = getUserId(req);
      const currentUser = await storage.getUser(currentUserId);
      
      // Only admins and coordinators can view CCNL settings
      if (currentUser?.role !== "admin" && currentUser?.role !== "coordinator") {
        return res.status(403).json({ message: "Forbidden: Admin or Coordinator access required" });
      }
      
      const settings = await storage.getAllCcnlSettings();
      res.json(settings);
    } catch (error) {
      console.error("Error fetching CCNL settings:", error);
      res.status(500).json({ message: "Failed to fetch CCNL settings" });
    }
  });

  app.get("/api/ccnl-settings/:key", isAuthenticated, async (req: any, res) => {
    try {
      const currentUserId = getUserId(req);
      const currentUser = await storage.getUser(currentUserId);
      
      // Only admins and coordinators can view CCNL settings
      if (currentUser?.role !== "admin" && currentUser?.role !== "coordinator") {
        return res.status(403).json({ message: "Forbidden: Admin or Coordinator access required" });
      }
      
      const setting = await storage.getCcnlSetting(req.params.key);
      if (!setting) {
        return res.status(404).json({ message: "CCNL setting not found" });
      }
      res.json(setting);
    } catch (error) {
      console.error("Error fetching CCNL setting:", error);
      res.status(500).json({ message: "Failed to fetch CCNL setting" });
    }
  });

  app.post("/api/ccnl-settings", isAuthenticated, async (req: any, res) => {
    try {
      const currentUserId = getUserId(req);
      const currentUser = await storage.getUser(currentUserId);
      
      // Only admins can create/update CCNL settings
      if (currentUser?.role !== "admin") {
        return res.status(403).json({ message: "Forbidden: Admin access required" });
      }
      
      const { insertCcnlSettingSchema } = await import("@shared/schema");
      const validationResult = insertCcnlSettingSchema.safeParse(req.body);
      
      if (!validationResult.success) {
        return res.status(400).json({ 
          message: "Invalid CCNL setting data",
          errors: validationResult.error.errors 
        });
      }
      
      const setting = await storage.upsertCcnlSetting(validationResult.data);
      res.json(setting);
    } catch (error) {
      console.error("Error creating/updating CCNL setting:", error);
      res.status(500).json({ message: "Failed to create/update CCNL setting" });
    }
  });

  app.delete("/api/ccnl-settings/:key", isAuthenticated, async (req: any, res) => {
    try {
      const currentUserId = getUserId(req);
      const currentUser = await storage.getUser(currentUserId);
      
      // Only admins can delete CCNL settings
      if (currentUser?.role !== "admin") {
        return res.status(403).json({ message: "Forbidden: Admin access required" });
      }
      
      await storage.deleteCcnlSetting(req.params.key);
      res.json({ success: true });
    } catch (error) {
      console.error("Error deleting CCNL setting:", error);
      res.status(500).json({ message: "Failed to delete CCNL setting" });
    }
  });

  // ============= CCNL VALIDATION ROUTES =============
  app.post("/api/ccnl/validate-shift", isAuthenticated, async (req, res) => {
    try {
      const { validateShiftForGuard } = await import("./ccnlRules");
      const { guardId, shiftStartTime, shiftEndTime, excludeShiftId } = req.body;
      
      if (!guardId || !shiftStartTime || !shiftEndTime) {
        return res.status(400).json({ 
          message: "Missing required fields: guardId, shiftStartTime, shiftEndTime" 
        });
      }
      
      const result = await validateShiftForGuard(
        guardId,
        new Date(shiftStartTime),
        new Date(shiftEndTime),
        excludeShiftId
      );
      
      res.json(result);
    } catch (error) {
      console.error("Error validating shift:", error);
      res.status(500).json({ message: "Failed to validate shift" });
    }
  });

  app.get("/api/ccnl/guard-availability/:guardId", isAuthenticated, async (req, res) => {
    try {
      const { getGuardAvailabilityReport } = await import("./ccnlRules");
      const { guardId } = req.params;
      const startDate = req.query.startDate ? new Date(req.query.startDate as string) : new Date();
      const endDate = req.query.endDate ? new Date(req.query.endDate as string) : new Date();
      
      const report = await getGuardAvailabilityReport(guardId, startDate, endDate);
      res.json(report);
    } catch (error) {
      console.error("Error fetching guard availability:", error);
      res.status(500).json({ message: "Failed to fetch guard availability" });
    }
  });

  // ============= OPERATIONAL PLANNING ROUTES =============
  app.get("/api/operational-planning/availability", isAuthenticated, async (req, res) => {
    try {
      const { getGuardAvailabilityReport } = await import("./ccnlRules");
      
      // Sanitizza input: gestisce sia "2025-10-17" che "2025-10-17/2025-10-17"
      const rawDateStr = req.query.date as string || format(new Date(), "yyyy-MM-dd");
      const normalizedDateStr = rawDateStr.split("/")[0]; // Prende solo la prima parte se c'è uno slash
      
      // TIMEZONE FIX: Valida formato senza parseISO per evitare shift timezone
      const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
      if (!dateRegex.test(normalizedDateStr)) {
        return res.status(400).json({ message: "Invalid date format. Use yyyy-MM-dd" });
      }
      
      const dateStr = normalizedDateStr;
      
      // Ottieni location dalla query (default: roccapiemonte)
      const location = req.query.location as string || "roccapiemonte";
      
      // Imposta inizio e fine giornata in UTC
      const startOfDay = new Date(dateStr + "T00:00:00.000Z");
      const endOfDay = new Date(dateStr + "T23:59:59.999Z");
      
      // Ottieni tutti i veicoli della sede selezionata
      const allVehicles = await storage.getAllVehicles();
      const locationVehicles = allVehicles.filter(v => v.location === location);
      
      // Ottieni turni del giorno SOLO della sede selezionata (join con sites per filtrare per location)
      const dayShifts = await db
        .select({
          shift: shifts
        })
        .from(shifts)
        .innerJoin(sites, eq(shifts.siteId, sites.id))
        .where(
          and(
            gte(shifts.startTime, startOfDay),
            lte(shifts.startTime, endOfDay),
            eq(sites.location, location)
          )
        );
      
      // Mappa veicoli con disponibilità
      const vehiclesWithAvailability = await Promise.all(
        locationVehicles.map(async (vehicle) => {
          const assignedShiftRecord = dayShifts.find((s: any) => s.shift.vehicleId === vehicle.id);
          
          return {
            ...vehicle,
            isAvailable: !assignedShiftRecord,
            assignedShift: assignedShiftRecord ? {
              id: assignedShiftRecord.shift.id,
              startTime: assignedShiftRecord.shift.startTime,
              endTime: assignedShiftRecord.shift.endTime,
              siteId: assignedShiftRecord.shift.siteId
            } : null
          };
        })
      );
      
      // Ottieni tutte le guardie della sede selezionata
      const allGuards = await storage.getAllGuards();
      const locationGuards = allGuards.filter(g => g.location === location);
      
      // Ottieni assegnazioni turni del giorno SOLO della sede selezionata (join con sites per filtrare per location)
      const dayShiftAssignments = await db
        .select()
        .from(shiftAssignments)
        .innerJoin(shifts, eq(shiftAssignments.shiftId, shifts.id))
        .innerJoin(sites, eq(shifts.siteId, sites.id))
        .where(
          and(
            gte(shifts.startTime, startOfDay),
            lte(shifts.startTime, endOfDay),
            eq(sites.location, location)
          )
        );
      
      // Calcola disponibilità agenti con report CCNL
      const guardsWithAvailability = await Promise.all(
        locationGuards.map(async (guard) => {
          const assignedShift = dayShiftAssignments.find(
            (assignment: any) => assignment.shift_assignments.guardId === guard.id
          );
          
          // Calcola report disponibilità CCNL
          const availabilityReport = await getGuardAvailabilityReport(
            guard.id,
            startOfDay,
            endOfDay
          );
          
          return {
            ...guard,
            isAvailable: !assignedShift,
            assignedShift: assignedShift ? {
              id: assignedShift.shifts.id,
              startTime: assignedShift.shifts.startTime,
              endTime: assignedShift.shifts.endTime,
              siteId: assignedShift.shifts.siteId
            } : null,
            availability: {
              weeklyHours: availabilityReport.weeklyHours.current,
              remainingWeeklyHours: availabilityReport.remainingWeeklyHours,
              remainingMonthlyHours: availabilityReport.remainingMonthlyHours,
              consecutiveDaysWorked: availabilityReport.consecutiveDaysWorked
            }
          };
        })
      );
      
      // Ordina veicoli: disponibili prima, poi per targa
      const sortedVehicles = vehiclesWithAvailability.sort((a, b) => {
        if (a.isAvailable && !b.isAvailable) return -1;
        if (!a.isAvailable && b.isAvailable) return 1;
        return a.licensePlate.localeCompare(b.licensePlate);
      });
      
      // Ordina agenti: disponibili prima, poi per ore settimanali (meno ore = più disponibili)
      const sortedGuards = guardsWithAvailability.sort((a, b) => {
        if (a.isAvailable && !b.isAvailable) return -1;
        if (!a.isAvailable && b.isAvailable) return 1;
        // Se entrambi disponibili, ordina per ore settimanali (meno ore = prima)
        if (a.isAvailable && b.isAvailable) {
          return a.availability.weeklyHours - b.availability.weeklyHours;
        }
        return 0;
      });
      
      res.json({
        date: dateStr,
        vehicles: sortedVehicles,
        guards: sortedGuards
      });
    } catch (error) {
      console.error("Error fetching operational planning availability:", error);
      res.status(500).json({ message: "Failed to fetch availability", error: String(error) });
    }
  });

  // Endpoint per ottenere siti non completamente coperti per una data e sede
  app.get("/api/operational-planning/uncovered-sites", isAuthenticated, async (req, res) => {
    try {
      // Sanitizza input: gestisce sia "2025-10-17" che "2025-10-17/2025-10-17"
      const rawDateStr = req.query.date as string || format(new Date(), "yyyy-MM-dd");
      const normalizedDateStr = rawDateStr.split("/")[0]; // Prende solo la prima parte se c'è uno slash
      
      // TIMEZONE FIX: Valida formato senza parseISO per evitare shift timezone
      const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
      if (!dateRegex.test(normalizedDateStr)) {
        return res.status(400).json({ message: "Invalid date format. Use yyyy-MM-dd" });
      }
      
      const dateStr = normalizedDateStr;
      
      // Ottieni location dalla query (default: roccapiemonte)
      const location = req.query.location as string || "roccapiemonte";
      
      // Ottieni tutti i siti attivi della sede selezionata
      const allSites = await db
        .select()
        .from(sites)
        .where(
          and(
            eq(sites.isActive, true),
            eq(sites.location, location)
          )
        );
      
      // Filtra siti con contratto valido per la data selezionata
      const sitesWithValidContract = allSites.filter((site: any) => {
        // Se il sito non ha date contrattuali, lo escludiamo
        if (!site.contractStartDate || !site.contractEndDate) {
          return false;
        }
        
        // Normalizza date per confronto day-only
        const selectedDate = new Date(dateStr);
        selectedDate.setHours(0, 0, 0, 0);
        
        const contractStart = new Date(site.contractStartDate);
        contractStart.setHours(0, 0, 0, 0);
        
        const contractEnd = new Date(site.contractEndDate);
        contractEnd.setHours(23, 59, 59, 999);
        
        // Verifica che la data selezionata sia dentro il periodo contrattuale
        return selectedDate >= contractStart && selectedDate <= contractEnd;
      });
      
      // Ottieni turni del giorno con assegnazioni SOLO della sede selezionata
      const startOfDayDate = new Date(dateStr);
      startOfDayDate.setHours(0, 0, 0, 0);
      
      const endOfDayDate = new Date(dateStr);
      endOfDayDate.setHours(23, 59, 59, 999);
      
      const dayShifts = await db
        .select({
          shift: shifts,
          assignmentCount: sql<number>`count(${shiftAssignments.id})::int`
        })
        .from(shifts)
        .innerJoin(sites, eq(shifts.siteId, sites.id))
        .leftJoin(shiftAssignments, eq(shifts.id, shiftAssignments.shiftId))
        .where(
          and(
            gte(shifts.startTime, startOfDayDate),
            lte(shifts.startTime, endOfDayDate),
            ne(shifts.status, "cancelled"),
            eq(sites.location, location)
          )
        )
        .groupBy(shifts.id);
      
      // Calcola copertura per ogni sito con contratto valido
      const sitesWithCoverage = sitesWithValidContract.map((site: any) => {
        const siteShifts = dayShifts.filter((s: any) => s.shift.siteId === site.id);
        
        // Verifica copertura per ogni turno
        const shiftsWithCoverage = siteShifts.map((s: any) => ({
          id: s.shift.id,
          startTime: s.shift.startTime,
          endTime: s.shift.endTime,
          assignedGuardsCount: s.assignmentCount,
          requiredGuards: site.minGuards,
          isCovered: s.assignmentCount >= site.minGuards,
          isPartial: s.assignmentCount > 0 && s.assignmentCount < site.minGuards
        }));
        
        // Un sito è completamente coperto solo se TUTTI i turni hanno il numero minimo di guardie
        const allShiftsCovered = siteShifts.length > 0 && shiftsWithCoverage.every((s: any) => s.isCovered);
        
        // Un sito è parzialmente coperto se ha turni ma non tutti sono completamente coperti
        const hasPartialCoverage = siteShifts.length > 0 && !allShiftsCovered && shiftsWithCoverage.some((s: any) => s.assignedGuardsCount > 0);
        
        // Calcola totale guardie assegnate per info
        const totalAssignedGuards = siteShifts.reduce((sum: number, s: any) => sum + s.assignmentCount, 0);
        
        return {
          ...site,
          isCovered: allShiftsCovered,
          isPartiallyCovered: hasPartialCoverage,
          totalAssignedGuards,
          requiredGuards: site.minGuards,
          shiftsCount: siteShifts.length,
          shifts: shiftsWithCoverage
        };
      });
      
      // Filtra solo siti non completamente coperti
      const uncoveredSites = sitesWithCoverage.filter(
        (site: any) => !site.isCovered
      );
      
      // Ordina: parzialmente coperti prima, poi non coperti
      const sortedUncoveredSites = uncoveredSites.sort((a: any, b: any) => {
        if (a.isPartiallyCovered && !b.isPartiallyCovered) return -1;
        if (!a.isPartiallyCovered && b.isPartiallyCovered) return 1;
        return a.name.localeCompare(b.name);
      });
      
      res.json({
        date: dateStr,
        uncoveredSites: sortedUncoveredSites,
        totalSites: sitesWithValidContract.length,
        totalUncovered: uncoveredSites.length
      });
    } catch (error) {
      console.error("Error fetching uncovered sites:", error);
      res.status(500).json({ message: "Failed to fetch uncovered sites", error: String(error) });
    }
  });

  // Endpoint per Planning Generale - vista settimanale con calcolo guardie mancanti
  app.get("/api/general-planning", isAuthenticated, async (req, res) => {
    try {
      // Sanitizza input data inizio settimana
      const rawWeekStart = req.query.weekStart as string || format(new Date(), "yyyy-MM-dd");
      const normalizedWeekStart = rawWeekStart.split("/")[0];
      
      // Valida la data
      const parsedWeekStart = parseISO(normalizedWeekStart);
      if (!isValid(parsedWeekStart)) {
        return res.status(400).json({ message: "Invalid date format. Use yyyy-MM-dd" });
      }
      
      const weekStartDate = format(parsedWeekStart, "yyyy-MM-dd");
      
      // Ottieni location dalla query (default: roccapiemonte)
      const location = req.query.location as string || "roccapiemonte";
      
      // Calcola fine settimana (weekStart + 6 giorni)
      const weekEndDate = format(addDays(parsedWeekStart, 6), "yyyy-MM-dd");
      
      // Timestamp per filtro contratti
      const weekStartTimestampForContract = new Date(weekStartDate);
      const weekEndTimestampForContract = new Date(weekEndDate);
      
      // Ottieni tutti i siti attivi della sede con contratto valido nelle date della settimana
      const activeSites = await db
        .select()
        .from(sites)
        .leftJoin(serviceTypes, eq(sites.serviceTypeId, serviceTypes.id))
        .where(
          and(
            eq(sites.isActive, true),
            eq(sites.location, location as any),
            // Contratto deve essere valido in almeno un giorno della settimana
            // contractStartDate <= weekEnd AND contractEndDate >= weekStart
            lte(sites.contractStartDate, weekEndTimestampForContract),
            gte(sites.contractEndDate, weekStartTimestampForContract)
          )
        );
      
      // Ottieni tutti i turni della settimana per la sede
      const weekStartTimestamp = new Date(weekStartDate);
      weekStartTimestamp.setHours(0, 0, 0, 0);
      
      const weekEndTimestamp = new Date(weekEndDate);
      weekEndTimestamp.setHours(23, 59, 59, 999);
      
      const weekShifts = await db
        .select({
          shift: shifts,
          site: sites,
        })
        .from(shifts)
        .innerJoin(sites, eq(shifts.siteId, sites.id))
        .where(
          and(
            gte(shifts.startTime, weekStartTimestamp),
            lte(shifts.startTime, weekEndTimestamp),
            ne(shifts.status, "cancelled"),
            eq(sites.location, location as any)
          )
        );
      
      // Ottieni tutte le assegnazioni dei turni della settimana
      const shiftIds = weekShifts.map((s: any) => s.shift.id);
      
      const assignments = shiftIds.length > 0 ? await db
        .select({
          assignment: shiftAssignments,
          guard: guards,
          shift: shifts,
        })
        .from(shiftAssignments)
        .innerJoin(guards, eq(shiftAssignments.guardId, guards.id))
        .innerJoin(shifts, eq(shiftAssignments.shiftId, shifts.id))
        .where(
          sql`${shiftAssignments.shiftId} IN (${sql.join(shiftIds.map((id: string) => sql`${id}`), sql`, `)})`
        ) : [];
      
      // Ottieni veicoli assegnati
      const vehicleAssignments = weekShifts
        .filter((s: any) => s.shift.vehicleId)
        .map((s: any) => s.shift.vehicleId);
      
      const assignedVehicles = vehicleAssignments.length > 0 ? await db
        .select()
        .from(vehicles)
        .where(
          sql`${vehicles.id} IN (${sql.join(vehicleAssignments.map((id: string) => sql`${id}`), sql`, `)})`
        ) : [];
      
      // Costruisci struttura dati per 7 giorni
      const weekData = [];
      
      for (let dayOffset = 0; dayOffset < 7; dayOffset++) {
        const currentDay = addDays(parsedWeekStart, dayOffset);
        const dayStr = format(currentDay, "yyyy-MM-dd");
        
        const dayStartTimestamp = new Date(dayStr);
        dayStartTimestamp.setHours(0, 0, 0, 0);
        
        const dayEndTimestamp = new Date(dayStr);
        dayEndTimestamp.setHours(23, 59, 59, 999);
        
        const sitesData = activeSites.map(({ sites: site, service_types: serviceType }: any) => {
          // Trova turni del giorno per questo sito
          const dayShifts = weekShifts.filter((s: any) => 
            s.shift.siteId === site.id && 
            s.shift.startTime >= dayStartTimestamp &&
            s.shift.startTime <= dayEndTimestamp
          );
          
          // Ottieni assegnazioni guardie per i turni del giorno
          const dayAssignments = assignments.filter((a: any) => 
            dayShifts.some((ds: any) => ds.shift.id === a.shift.id)
          );
          
          // Calcola ore per ogni guardia con orari e assignmentId
          const guardsWithHours = dayAssignments.map((a: any) => {
            const plannedStart = new Date(a.assignment.plannedStartTime);
            const plannedEnd = new Date(a.assignment.plannedEndTime);
            const hours = differenceInHours(plannedEnd, plannedStart);
            
            return {
              assignmentId: a.assignment.id,
              guardId: a.guard.id,
              guardName: a.guard.fullName,
              badgeNumber: a.guard.badgeNumber,
              hours,
              plannedStartTime: a.assignment.plannedStartTime,
              plannedEndTime: a.assignment.plannedEndTime,
            };
          });
          
          // Veicoli assegnati ai turni del giorno
          const dayVehicles = dayShifts
            .filter((ds: any) => ds.shift.vehicleId)
            .map((ds: any) => {
              const vehicle = assignedVehicles.find((v: any) => v.id === ds.shift.vehicleId);
              return vehicle ? {
                vehicleId: vehicle.id,
                licensePlate: vehicle.licensePlate,
                brand: vehicle.brand,
                model: vehicle.model,
              } : null;
            })
            .filter(Boolean);
          
          // Calcolo guardie mancanti
          const maxOreGuardia = 9; // Max ore per guardia
          const minGuardie = site.minGuards || 1;
          
          // Calcola ore servizio del sito (per calcolo corretto anche senza turni)
          const serviceStart = site.serviceStartTime || "00:00";
          const serviceEnd = site.serviceEndTime || "23:59";
          const [startH, startM] = serviceStart.split(":").map(Number);
          const [endH, endM] = serviceEnd.split(":").map(Number);
          let serviceHours = (endH + endM/60) - (startH + startM/60);
          if (serviceHours <= 0) serviceHours += 24; // Servizio notturno (es. 22:00-06:00)
          
          // Somma ore totali dei turni del giorno (se esistono)
          const totalShiftHours = dayShifts.reduce((sum: number, ds: any) => {
            const start = new Date(ds.shift.startTime);
            const end = new Date(ds.shift.endTime);
            return sum + differenceInHours(end, start);
          }, 0);
          
          // Usa ore servizio o ore turni (se già creati)
          const effectiveHours = totalShiftHours > 0 ? totalShiftHours : serviceHours;
          
          // Guardie uniche assegnate (conta ogni guardia una volta anche se ha più turni)
          const uniqueGuardsAssigned = new Set(guardsWithHours.map((g: any) => g.guardId)).size;
          
          // Calcolo guardie necessarie basato su ore servizio
          // Slot necessari per coprire le ore (ogni guardia max 9h)
          const slotsNeeded = Math.ceil(effectiveHours / maxOreGuardia);
          // Guardie totali necessarie (slot × min guardie contemporanee)
          const totalGuardsNeeded = slotsNeeded * minGuardie;
          const missingGuards = Math.max(0, totalGuardsNeeded - uniqueGuardsAssigned);
          
          return {
            siteId: site.id,
            siteName: site.name,
            serviceType: serviceType?.label || "N/A",
            serviceStartTime: serviceStart,
            serviceEndTime: serviceEnd,
            serviceHours: Math.round(serviceHours * 10) / 10, // Arrotonda a 1 decimale
            minGuards: site.minGuards,
            guards: guardsWithHours,
            vehicles: dayVehicles,
            totalShiftHours,
            guardsAssigned: uniqueGuardsAssigned,
            missingGuards,
            shiftsCount: dayShifts.length,
          };
        });
        
        weekData.push({
          date: dayStr,
          dayOfWeek: format(currentDay, "EEEE"),
          sites: sitesData,
        });
      }
      
      // Calcola guardie totali necessarie per l'intera settimana
      let totalGuardsNeededForWeek = 0;
      let totalGuardsAssignedForWeek = 0;
      
      for (const day of weekData) {
        for (const siteData of day.sites) {
          // Somma guardie necessarie (già calcolate per sito/giorno)
          // totalGuardsNeeded per sito = guardsAssigned + missingGuards
          totalGuardsNeededForWeek += (siteData.guardsAssigned + siteData.missingGuards);
          
          // Somma slot guardia assegnati (non guardie uniche)
          // Questo conta ogni assegnazione, anche se la stessa guardia lavora più turni
          totalGuardsAssignedForWeek += siteData.guardsAssigned;
        }
      }
      
      const totalGuardsMissingForWeek = Math.max(0, totalGuardsNeededForWeek - totalGuardsAssignedForWeek);
      
      res.json({
        weekStart: weekStartDate,
        weekEnd: weekEndDate,
        location,
        days: weekData,
        summary: {
          totalGuardsNeeded: totalGuardsNeededForWeek,
          totalGuardsAssigned: totalGuardsAssignedForWeek,
          totalGuardsMissing: totalGuardsMissingForWeek,
        }
      });
    } catch (error) {
      console.error("Error fetching general planning:", error);
      res.status(500).json({ message: "Failed to fetch general planning", error: String(error) });
    }
  });

  // Create multi-day shift from general planning
  app.post("/api/general-planning/shifts", isAuthenticated, async (req, res) => {
    try {
      // Validate request body
      const validationResult = createMultiDayShiftSchema.safeParse(req.body);
      if (!validationResult.success) {
        return res.status(400).json({ 
          message: "Invalid request data",
          errors: validationResult.error.errors 
        });
      }
      
      const { siteId, startDate, days, guardId, shiftType } = validationResult.data;
      
      // Get site to check contract and service details
      const site = await storage.getSite(siteId);
      if (!site) {
        return res.status(404).json({ message: "Site not found" });
      }
      
      // Get guard to verify it exists
      const guard = await storage.getGuard(guardId);
      if (!guard) {
        return res.status(404).json({ message: "Guard not found" });
      }
      
      // Pre-validate all dates are within contract period
      // TIMEZONE FIX: Parse date as YYYY-MM-DD components to avoid timezone shifts
      const [year, month, day] = startDate.split("-").map(Number);
      
      for (let dayOffset = 0; dayOffset < days; dayOffset++) {
        // Create date using local timezone components (no UTC conversion)
        const shiftDate = new Date(year, month - 1, day + dayOffset);
        const shiftDateStr = format(shiftDate, "yyyy-MM-dd");
        
        if (site.contractStartDate && site.contractEndDate) {
          const contractStart = new Date(site.contractStartDate);
          const contractEnd = new Date(site.contractEndDate);
          if (shiftDate < contractStart || shiftDate > contractEnd) {
            return res.status(400).json({ 
              message: `Cannot create shift for ${shiftDateStr}: outside contract period` 
            });
          }
        }
      }
      
      // Create shifts atomically in a transaction
      const createdShifts = await db.transaction(async (tx) => {
        const createdShiftsInTx = [];
        
        for (let dayOffset = 0; dayOffset < days; dayOffset++) {
          // TIMEZONE FIX: Build date from components to maintain correct day
          const shiftDate = new Date(year, month - 1, day + dayOffset);
          
          // Use site service schedule or default 24h
          const serviceStart = site.serviceStartTime || "00:00";
          const serviceEnd = site.serviceEndTime || "23:59";
          
          const [startHour, startMin] = serviceStart.split(":").map(Number);
          const [endHour, endMin] = serviceEnd.split(":").map(Number);
          
          // Build timestamps using date components (no timezone conversion)
          const shiftStart = new Date(year, month - 1, day + dayOffset, startHour, startMin, 0, 0);
          const shiftEnd = new Date(year, month - 1, day + dayOffset, endHour, endMin, 0, 0);
          
          // If service ends before it starts, it spans midnight (add 1 day to end)
          if (shiftEnd <= shiftStart) {
            shiftEnd.setDate(shiftEnd.getDate() + 1);
          }
          
          // Create shift in transaction
          const [shift] = await tx.insert(shifts).values({
            siteId: site.id,
            startTime: shiftStart,
            endTime: shiftEnd,
            shiftType: shiftType || site.shiftType || "fixed_post",
            status: "planned",
          }).returning();
          
          // Create shift assignment in transaction
          await tx.insert(shiftAssignments).values({
            shiftId: shift.id,
            guardId: guard.id,
          });
          
          createdShiftsInTx.push(shift);
        }
        
        return createdShiftsInTx;
      });
      
      res.json({ 
        message: `Created ${createdShifts.length} shifts`,
        shifts: createdShifts 
      });
    } catch (error) {
      console.error("Error creating multi-day shifts:", error);
      res.status(500).json({ message: "Failed to create shifts", error: String(error) });
    }
  });

  // Delete a shift assignment
  app.delete("/api/shift-assignments/:assignmentId", isAuthenticated, async (req, res) => {
    try {
      const { assignmentId } = req.params;
      
      if (!assignmentId) {
        return res.status(400).json({ message: "Assignment ID is required" });
      }
      
      // Delete the assignment
      const deleted = await db
        .delete(shiftAssignments)
        .where(eq(shiftAssignments.id, assignmentId))
        .returning();
      
      if (deleted.length === 0) {
        return res.status(404).json({ message: "Assignment not found" });
      }
      
      res.json({ 
        message: "Assignment deleted successfully",
        assignment: deleted[0]
      });
    } catch (error: any) {
      console.error("Error deleting assignment:", error);
      res.status(500).json({ message: "Failed to delete assignment", error: String(error) });
    }
  });

  // Assign guard to site/date with specific time slot (supports multi-day assignments)
  app.post("/api/general-planning/assign-guard", isAuthenticated, async (req, res) => {
    try {
      const { siteId, date, guardId, startTime, durationHours, consecutiveDays = 1, vehicleId, force = false } = req.body;
      
      if (!siteId || !date || !guardId || !startTime || !durationHours) {
        return res.status(400).json({ 
          message: "Missing required fields: siteId, date, guardId, startTime, durationHours" 
        });
      }
      
      if (consecutiveDays < 1 || consecutiveDays > 30) {
        return res.status(400).json({ message: "consecutiveDays must be between 1 and 30" });
      }
      
      // Get site to check contract and service details
      const site = await storage.getSite(siteId);
      if (!site) {
        return res.status(404).json({ message: "Site not found" });
      }
      
      // Get guard to verify it exists
      const guard = await storage.getGuard(guardId);
      if (!guard) {
        return res.status(404).json({ message: "Guard not found" });
      }
      
      // Parse start date components
      const [year, month, day] = date.split("-").map(Number);
      if (!year || !month || !day || month < 1 || month > 12 || day < 1 || day > 31) {
        return res.status(400).json({ message: "Invalid date format. Expected YYYY-MM-DD" });
      }
      const [hours, minutes] = startTime.split(":").map(Number);
      
      // Atomic transaction: create assignments for all consecutive days
      const result = await db.transaction(async (tx) => {
        const createdAssignments = [];
        
        // Loop through each consecutive day
        for (let dayOffset = 0; dayOffset < consecutiveDays; dayOffset++) {
          // Calculate date components for this iteration (avoid timezone issues)
          const targetDay = day + dayOffset;
          const baseDate = new Date(year, month - 1, 1); // First day of month
          baseDate.setDate(targetDay); // Set to target day (handles month overflow)
          
          // Extract actual date components after overflow handling
          const actualYear = baseDate.getFullYear();
          const actualMonth = baseDate.getMonth();
          const actualDay = baseDate.getDate();
          
          // Build dates in LOCAL timezone to match user's selection
          const shiftDate = new Date(actualYear, actualMonth, actualDay, 0, 0, 0, 0);
          
          // Check contract validity for this date
          if (site.contractStartDate && site.contractEndDate) {
            const contractStart = new Date(site.contractStartDate);
            const contractEnd = new Date(site.contractEndDate);
            if (shiftDate < contractStart || shiftDate > contractEnd) {
              throw new Error(
                `Cannot assign guard for date ${shiftDate.toLocaleDateString()}: outside contract period`
              );
            }
          }
          
          // Calculate planned start and end times in LOCAL timezone
          const plannedStart = new Date(actualYear, actualMonth, actualDay, hours, minutes, 0, 0);
          const plannedEnd = new Date(actualYear, actualMonth, actualDay, hours + durationHours, minutes, 0, 0);
          
          // Find or create shift for this site/date (full day boundaries in LOCAL timezone)
          const dayStart = new Date(actualYear, actualMonth, actualDay, 0, 0, 0, 0);
          const dayEnd = new Date(actualYear, actualMonth, actualDay, 23, 59, 59, 999);
          
          let existingShifts = await tx
            .select()
            .from(shifts)
            .where(
              and(
                eq(shifts.siteId, siteId),
                gte(shifts.startTime, dayStart),
                lte(shifts.startTime, dayEnd)
              )
            );
          
          let shift;
          if (existingShifts.length > 0) {
            shift = existingShifts[0];
          } else {
            // Create new shift for full service period
            const serviceStart = site.serviceStartTime || "00:00";
            const serviceEnd = site.serviceEndTime || "23:59";
            
            const [startHour, startMin] = serviceStart.split(":").map(Number);
            const [endHour, endMin] = serviceEnd.split(":").map(Number);
            
            const shiftStart = new Date(actualYear, actualMonth, actualDay, startHour, startMin, 0, 0);
            let shiftEnd = new Date(actualYear, actualMonth, actualDay, endHour, endMin, 0, 0);
            
            // If end time is before/equal to start time, shift extends to next day
            if (shiftEnd <= shiftStart) {
              shiftEnd = new Date(actualYear, actualMonth, actualDay + 1, endHour, endMin, 0, 0);
            }
            
            [shift] = await tx.insert(shifts).values({
              siteId: site.id,
              startTime: shiftStart,
              endTime: shiftEnd,
              shiftType: site.shiftType || "fixed_post",
              status: "planned",
              vehicleId: vehicleId || null,
            }).returning();
          }
          
          // Recheck overlaps within transaction for this day
          const existingAssignments = await tx
            .select()
            .from(shiftAssignments)
            .where(eq(shiftAssignments.guardId, guard.id));
          
          // Check for time overlaps
          for (const existing of existingAssignments) {
            const hasOverlap = 
              plannedStart < existing.plannedEndTime && 
              plannedEnd > existing.plannedStartTime;
            
            if (hasOverlap) {
              throw new Error(
                `Conflitto: guardia già assegnata ${existing.plannedStartTime.toLocaleString()} - ${existing.plannedEndTime.toLocaleString()}`
              );
            }
          }
          
          // CCNL: Check daily hour limit (max 9h/day) - skip if force=true
          if (!force) {
            const maxDailyHours = 9;
            let dailyHoursAlreadyAssigned = 0;
            
            for (const existing of existingAssignments) {
              // Check if assignment is on the same day
              const existingDate = new Date(existing.plannedStartTime);
              if (
                existingDate.getUTCFullYear() === actualYear &&
                existingDate.getUTCMonth() === actualMonth &&
                existingDate.getUTCDate() === actualDay
              ) {
                const assignmentHours = differenceInHours(
                  existing.plannedEndTime,
                  existing.plannedStartTime
                );
                dailyHoursAlreadyAssigned += assignmentHours;
              }
            }
            
            // Check if new assignment would exceed daily limit
            if (dailyHoursAlreadyAssigned + durationHours > maxDailyHours) {
              const excessHours = (dailyHoursAlreadyAssigned + durationHours) - maxDailyHours;
              throw new Error(
                `Limite giornaliero superato: la guardia ha già ${dailyHoursAlreadyAssigned}h assegnate il ${shiftDate.toLocaleDateString('it-IT')}. ` +
                `Aggiungendo ${durationHours}h si supererebbero di ${excessHours}h le ${maxDailyHours}h massime giornaliere (CCNL).`
              );
            }
          }
          
          // Create assignment for this day
          const [assignment] = await tx.insert(shiftAssignments).values({
            shiftId: shift.id,
            guardId: guard.id,
            plannedStartTime: plannedStart,
            plannedEndTime: plannedEnd,
          }).returning();
          
          createdAssignments.push(assignment);
        }
        
        return { assignments: createdAssignments, count: createdAssignments.length };
      });
      
      res.json({ 
        message: `Guard assigned successfully for ${result.count} day(s)`,
        assignments: result.assignments,
        count: result.count
      });
    } catch (error: any) {
      console.error("Error assigning guard:", error);
      // Check for overlap/conflict errors (both English and Italian)
      const errorMessage = error.message?.toLowerCase() || '';
      if (errorMessage.includes('overlap') || 
          errorMessage.includes('conflict') || 
          errorMessage.includes('conflitto') ||
          errorMessage.includes('già assegnata') ||
          errorMessage.includes('limite giornaliero') ||
          errorMessage.includes('limite settimanale') ||
          errorMessage.includes('ccnl')) {
        return res.status(409).json({ 
          message: error.message,
          type: errorMessage.includes('limite') ? 'CCNL_VIOLATION' : 'CONFLICT'
        });
      }
      res.status(500).json({ message: "Failed to assign guard", error: String(error) });
    }
  });

  // ============= SERVICE PLANNING ROUTES =============
  
  // Vista per Guardia - mostra orari e dotazioni per ogni guardia
  app.get("/api/service-planning/by-guard", isAuthenticated, async (req, res) => {
    try {
      const rawWeekStart = req.query.weekStart as string || format(new Date(), "yyyy-MM-dd");
      const normalizedWeekStart = rawWeekStart.split("/")[0];
      
      const parsedWeekStart = parseISO(normalizedWeekStart);
      if (!isValid(parsedWeekStart)) {
        return res.status(400).json({ message: "Invalid date format. Use yyyy-MM-dd" });
      }
      
      const weekStartDate = format(parsedWeekStart, "yyyy-MM-dd");
      const location = req.query.location as string || "roccapiemonte";
      const weekEndDate = format(addDays(parsedWeekStart, 6), "yyyy-MM-dd");
      
      const weekStartTimestamp = new Date(weekStartDate);
      weekStartTimestamp.setHours(0, 0, 0, 0);
      
      const weekEndTimestamp = new Date(weekEndDate);
      weekEndTimestamp.setHours(23, 59, 59, 999);
      
      // Ottieni tutte le guardie della sede
      const allGuards = await db
        .select()
        .from(guards)
        .where(eq(guards.location, location as any))
        .orderBy(guards.fullName);
      
      // Ottieni tutti i turni della settimana per la sede (con JOIN su sites per filtrare location)
      const weekShifts = await db
        .select({
          shift: shifts,
          site: sites,
          vehicle: vehicles,
        })
        .from(shifts)
        .innerJoin(sites, eq(shifts.siteId, sites.id))
        .leftJoin(vehicles, eq(shifts.vehicleId, vehicles.id))
        .where(
          and(
            gte(shifts.startTime, weekStartTimestamp),
            lte(shifts.startTime, weekEndTimestamp),
            ne(shifts.status, "cancelled"),
            eq(sites.location, location as any)
          )
        );
      
      // Ottieni tutte le assegnazioni per i turni della settimana
      const shiftIds = weekShifts.map((s: any) => s.shift.id);
      const assignments = shiftIds.length > 0 ? await db
        .select({
          assignment: shiftAssignments,
          guard: guards,
        })
        .from(shiftAssignments)
        .innerJoin(guards, eq(shiftAssignments.guardId, guards.id))
        .where(
          sql`${shiftAssignments.shiftId} IN (${sql.join(shiftIds.map((id: string) => sql`${id}`), sql`, `)})`
        ) : [];
      
      // Costruisci dati per ogni guardia
      const guardSchedules = allGuards.map((guard: any) => {
        // Trova assegnazioni della guardia
        const guardAssignments = assignments.filter((a: any) => a.guard.id === guard.id);
        
        // Costruisci lista turni con dettagli
        const shifts = guardAssignments.map((a: any) => {
          const shiftData = weekShifts.find((s: any) => s.shift.id === a.assignment.shiftId);
          if (!shiftData) return null;
          
          const plannedStart = new Date(a.assignment.plannedStartTime);
          const plannedEnd = new Date(a.assignment.plannedEndTime);
          const minutes = differenceInMinutes(plannedEnd, plannedStart);
          const hours = Math.round((minutes / 60) * 10) / 10; // Arrotonda a 1 decimale
          
          return {
            shiftId: shiftData.shift.id,
            date: format(plannedStart, "yyyy-MM-dd"),
            from: format(plannedStart, "HH:mm"),
            to: format(plannedEnd, "HH:mm"),
            siteName: shiftData.site.name,
            siteId: shiftData.site.id,
            vehicle: shiftData.vehicle ? {
              licensePlate: shiftData.vehicle.licensePlate,
              brand: shiftData.vehicle.brand,
              model: shiftData.vehicle.model,
            } : undefined,
            hours,
          };
        }).filter(Boolean);
        
        const totalHours = Math.round(shifts.reduce((sum: number, s: any) => sum + s.hours, 0) * 10) / 10;
        
        return {
          guardId: guard.id,
          guardName: guard.fullName,
          badgeNumber: guard.badgeNumber,
          shifts,
          totalHours,
        };
      });
      
      // Filtra solo guardie con turni assegnati
      const guardsWithShifts = guardSchedules.filter((g: any) => g.shifts.length > 0);
      
      res.json(guardsWithShifts);
    } catch (error) {
      console.error("Error fetching guard schedules:", error);
      res.status(500).json({ message: "Failed to fetch guard schedules", error: String(error) });
    }
  });
  
  // Vista per Sito - mostra agenti e dotazioni per ogni sito
  app.get("/api/service-planning/by-site", isAuthenticated, async (req, res) => {
    try {
      const rawWeekStart = req.query.weekStart as string || format(new Date(), "yyyy-MM-dd");
      const normalizedWeekStart = rawWeekStart.split("/")[0];
      
      const parsedWeekStart = parseISO(normalizedWeekStart);
      if (!isValid(parsedWeekStart)) {
        return res.status(400).json({ message: "Invalid date format. Use yyyy-MM-dd" });
      }
      
      const weekStartDate = format(parsedWeekStart, "yyyy-MM-dd");
      const location = req.query.location as string || "roccapiemonte";
      const weekEndDate = format(addDays(parsedWeekStart, 6), "yyyy-MM-dd");
      
      const weekStartTimestamp = new Date(weekStartDate);
      weekStartTimestamp.setHours(0, 0, 0, 0);
      
      const weekEndTimestamp = new Date(weekEndDate);
      weekEndTimestamp.setHours(23, 59, 59, 999);
      
      // Ottieni tutti i siti attivi della sede
      const activeSites = await db
        .select()
        .from(sites)
        .where(
          and(
            eq(sites.isActive, true),
            eq(sites.location, location as any)
          )
        )
        .orderBy(sites.name);
      
      // Ottieni tutti i turni della settimana per la sede
      const weekShifts = await db
        .select({
          shift: shifts,
          site: sites,
          vehicle: vehicles,
        })
        .from(shifts)
        .innerJoin(sites, eq(shifts.siteId, sites.id))
        .leftJoin(vehicles, eq(shifts.vehicleId, vehicles.id))
        .where(
          and(
            gte(shifts.startTime, weekStartTimestamp),
            lte(shifts.startTime, weekEndTimestamp),
            ne(shifts.status, "cancelled"),
            eq(sites.location, location as any)
          )
        );
      
      // Ottieni tutte le assegnazioni per i turni della settimana
      const shiftIds = weekShifts.map((s: any) => s.shift.id);
      const assignments = shiftIds.length > 0 ? await db
        .select({
          assignment: shiftAssignments,
          guard: guards,
        })
        .from(shiftAssignments)
        .innerJoin(guards, eq(shiftAssignments.guardId, guards.id))
        .where(
          sql`${shiftAssignments.shiftId} IN (${sql.join(shiftIds.map((id: string) => sql`${id}`), sql`, `)})`
        ) : [];
      
      // Costruisci dati per ogni sito
      const siteSchedules = activeSites.map((site: any) => {
        // Trova turni del sito
        const siteShifts = weekShifts.filter((s: any) => s.site.id === site.id);
        
        // Costruisci lista turni con guardie e veicoli
        const shifts = siteShifts.map((shiftData: any) => {
          const shiftAssignments = assignments.filter((a: any) => a.assignment.shiftId === shiftData.shift.id);
          
          const guards = shiftAssignments.map((a: any) => {
            const plannedStart = new Date(a.assignment.plannedStartTime);
            const plannedEnd = new Date(a.assignment.plannedEndTime);
            const minutes = differenceInMinutes(plannedEnd, plannedStart);
            const hours = Math.round((minutes / 60) * 10) / 10; // Arrotonda a 1 decimale
            
            return {
              guardName: a.guard.fullName,
              badgeNumber: a.guard.badgeNumber,
              hours,
            };
          });
          
          const shiftStart = new Date(shiftData.shift.startTime);
          const shiftEnd = new Date(shiftData.shift.endTime);
          const minutes = differenceInMinutes(shiftEnd, shiftStart);
          const totalHours = Math.round((minutes / 60) * 10) / 10;
          
          return {
            shiftId: shiftData.shift.id,
            date: format(shiftStart, "yyyy-MM-dd"),
            from: format(shiftStart, "HH:mm"),
            to: format(shiftEnd, "HH:mm"),
            guards,
            vehicle: shiftData.vehicle ? {
              licensePlate: shiftData.vehicle.licensePlate,
              brand: shiftData.vehicle.brand,
              model: shiftData.vehicle.model,
            } : undefined,
            totalGuards: guards.length,
            totalHours,
          };
        });
        
        const totalHours = Math.round(shifts.reduce((sum: number, s: any) => sum + s.totalHours, 0) * 10) / 10;
        
        return {
          siteId: site.id,
          siteName: site.name,
          location: site.location,
          shifts,
          totalShifts: shifts.length,
          totalHours,
        };
      });
      
      // Filtra solo siti con turni programmati
      const sitesWithShifts = siteSchedules.filter((s: any) => s.shifts.length > 0);
      
      res.json(sitesWithShifts);
    } catch (error) {
      console.error("Error fetching site schedules:", error);
      res.status(500).json({ message: "Failed to fetch site schedules", error: String(error) });
    }
  });

  // ============= REPORTS ROUTES =============
  
  // Report mensile ore per guardia (ordinarie/straordinarie + buoni pasto)
  app.get("/api/reports/monthly-guard-hours", isAuthenticated, async (req, res) => {
    try {
      const rawMonth = req.query.month as string || format(new Date(), "yyyy-MM");
      const location = req.query.location as string || "roccapiemonte";
      
      // Parse mese (formato: YYYY-MM)
      const [year, month] = rawMonth.split("-").map(Number);
      const monthStart = new Date(year, month - 1, 1);
      monthStart.setHours(0, 0, 0, 0);
      
      const monthEnd = new Date(year, month, 0); // ultimo giorno del mese
      monthEnd.setHours(23, 59, 59, 999);
      
      // Ottieni tutte le guardie della sede
      const allGuards = await db
        .select()
        .from(guards)
        .where(eq(guards.location, location as any))
        .orderBy(guards.fullName);
      
      // Ottieni tutti i turni del mese per la sede
      const monthShifts = await db
        .select({
          shift: shifts,
          site: sites,
        })
        .from(shifts)
        .innerJoin(sites, eq(shifts.siteId, sites.id))
        .where(
          and(
            gte(shifts.startTime, monthStart),
            lte(shifts.startTime, monthEnd),
            ne(shifts.status, "cancelled"),
            eq(sites.location, location as any)
          )
        );
      
      // Ottieni tutte le assegnazioni del mese
      const shiftIds = monthShifts.map((s: any) => s.shift.id);
      const assignments = shiftIds.length > 0 ? await db
        .select({
          assignment: shiftAssignments,
          guard: guards,
        })
        .from(shiftAssignments)
        .innerJoin(guards, eq(shiftAssignments.guardId, guards.id))
        .where(
          sql`${shiftAssignments.shiftId} IN (${sql.join(shiftIds.map((id: string) => sql`${id}`), sql`, `)})`
        ) : [];
      
      // Calcola statistiche per ogni guardia
      const guardReports = allGuards.map((guard: any) => {
        const guardAssignments = assignments.filter((a: any) => a.guard.id === guard.id);
        
        // Raggruppa assegnazioni per settimana (lunedì = inizio settimana)
        const weeklyHours: Record<string, number> = {};
        const dailyHours: Record<string, number> = {}; // Per calcolare buoni pasto
        
        guardAssignments.forEach((a: any) => {
          const plannedStart = new Date(a.assignment.plannedStartTime);
          const plannedEnd = new Date(a.assignment.plannedEndTime);
          const minutes = differenceInMinutes(plannedEnd, plannedStart);
          const hours = minutes / 60;
          
          // Settimana (ISO week - lunedì come primo giorno)
          const weekStart = startOfWeek(plannedStart, { weekStartsOn: 1 });
          const weekKey = format(weekStart, "yyyy-MM-dd");
          weeklyHours[weekKey] = (weeklyHours[weekKey] || 0) + hours;
          
          // Giorno (per buoni pasto)
          const dayKey = format(plannedStart, "yyyy-MM-dd");
          dailyHours[dayKey] = (dailyHours[dayKey] || 0) + hours;
        });
        
        // Calcola ore ordinarie e straordinarie
        let ordinaryHours = 0;
        let overtimeHours = 0;
        
        Object.values(weeklyHours).forEach((weekHours: number) => {
          if (weekHours <= 40) {
            ordinaryHours += weekHours;
          } else {
            ordinaryHours += 40;
            overtimeHours += (weekHours - 40);
          }
        });
        
        // Calcola buoni pasto (giorni con ore ≥ 6)
        const mealVouchers = Object.values(dailyHours).filter(
          (dayHours: number) => dayHours >= 6
        ).length;
        
        const totalHours = ordinaryHours + overtimeHours;
        
        return {
          guardId: guard.id,
          guardName: guard.fullName,
          badgeNumber: guard.badgeNumber,
          ordinaryHours: Math.round(ordinaryHours * 10) / 10,
          overtimeHours: Math.round(overtimeHours * 10) / 10,
          totalHours: Math.round(totalHours * 10) / 10,
          mealVouchers,
          workingDays: Object.keys(dailyHours).length,
        };
      });
      
      // Filtra solo guardie con ore lavorate
      const guardsWithHours = guardReports.filter((g: any) => g.totalHours > 0);
      
      res.json({
        month: rawMonth,
        location,
        guards: guardsWithHours,
        summary: {
          totalGuards: guardsWithHours.length,
          totalOrdinaryHours: Math.round(guardsWithHours.reduce((sum: number, g: any) => sum + g.ordinaryHours, 0) * 10) / 10,
          totalOvertimeHours: Math.round(guardsWithHours.reduce((sum: number, g: any) => sum + g.overtimeHours, 0) * 10) / 10,
          totalHours: Math.round(guardsWithHours.reduce((sum: number, g: any) => sum + g.totalHours, 0) * 10) / 10,
          totalMealVouchers: guardsWithHours.reduce((sum: number, g: any) => sum + g.mealVouchers, 0),
        },
      });
    } catch (error) {
      console.error("Error fetching monthly guard hours:", error);
      res.status(500).json({ message: "Failed to fetch monthly guard hours", error: String(error) });
    }
  });
  
  // Report ore fatturabili per sito per tipologia servizio
  app.get("/api/reports/billable-site-hours", isAuthenticated, async (req, res) => {
    try {
      const rawMonth = req.query.month as string || format(new Date(), "yyyy-MM");
      const location = req.query.location as string || "roccapiemonte";
      
      // Parse mese (formato: YYYY-MM)
      const [year, month] = rawMonth.split("-").map(Number);
      const monthStart = new Date(year, month - 1, 1);
      monthStart.setHours(0, 0, 0, 0);
      
      const monthEnd = new Date(year, month, 0);
      monthEnd.setHours(23, 59, 59, 999);
      
      // Ottieni tutti i turni del mese per la sede con dettagli sito e servizio
      const monthShifts = await db
        .select({
          shift: shifts,
          site: sites,
          serviceType: serviceTypes,
        })
        .from(shifts)
        .innerJoin(sites, eq(shifts.siteId, sites.id))
        .leftJoin(serviceTypes, eq(sites.serviceTypeId, serviceTypes.id))
        .where(
          and(
            gte(shifts.startTime, monthStart),
            lte(shifts.startTime, monthEnd),
            ne(shifts.status, "cancelled"),
            eq(sites.location, location as any)
          )
        );
      
      // Raggruppa per sito
      const siteHoursMap: Record<string, any> = {};
      
      monthShifts.forEach((shiftData: any) => {
        const siteId = shiftData.site.id;
        const siteName = shiftData.site.name;
        const serviceTypeName = shiftData.serviceType?.name || "Non specificato";
        
        const shiftStart = new Date(shiftData.shift.startTime);
        const shiftEnd = new Date(shiftData.shift.endTime);
        const minutes = differenceInMinutes(shiftEnd, shiftStart);
        const hours = minutes / 60;
        
        if (!siteHoursMap[siteId]) {
          siteHoursMap[siteId] = {
            siteId,
            siteName,
            serviceTypes: {},
            totalHours: 0,
            totalShifts: 0,
          };
        }
        
        if (!siteHoursMap[siteId].serviceTypes[serviceTypeName]) {
          siteHoursMap[siteId].serviceTypes[serviceTypeName] = {
            hours: 0,
            shifts: 0,
          };
        }
        
        siteHoursMap[siteId].serviceTypes[serviceTypeName].hours += hours;
        siteHoursMap[siteId].serviceTypes[serviceTypeName].shifts += 1;
        siteHoursMap[siteId].totalHours += hours;
        siteHoursMap[siteId].totalShifts += 1;
      });
      
      // Converti mappa in array e arrotonda ore
      const siteReports = Object.values(siteHoursMap).map((site: any) => {
        const serviceTypesArray = Object.entries(site.serviceTypes).map(([name, data]: [string, any]) => ({
          name,
          hours: Math.round(data.hours * 10) / 10,
          shifts: data.shifts,
        }));
        
        return {
          siteId: site.siteId,
          siteName: site.siteName,
          serviceTypes: serviceTypesArray,
          totalHours: Math.round(site.totalHours * 10) / 10,
          totalShifts: site.totalShifts,
        };
      });
      
      // Ordina per ore totali (decrescente)
      siteReports.sort((a: any, b: any) => b.totalHours - a.totalHours);
      
      res.json({
        month: rawMonth,
        location,
        sites: siteReports,
        summary: {
          totalSites: siteReports.length,
          totalHours: Math.round(siteReports.reduce((sum: number, s: any) => sum + s.totalHours, 0) * 10) / 10,
          totalShifts: siteReports.reduce((sum: number, s: any) => sum + s.totalShifts, 0),
        },
      });
    } catch (error) {
      console.error("Error fetching billable site hours:", error);
      res.status(500).json({ message: "Failed to fetch billable site hours", error: String(error) });
    }
  });

  // ============= CERTIFICATION ROUTES =============
  app.post("/api/certifications", isAuthenticated, async (req, res) => {
    try {
      const cert = await storage.createCertification(req.body);
      res.json(cert);
    } catch (error) {
      console.error("Error creating certification:", error);
      res.status(500).json({ message: "Failed to create certification" });
    }
  });

  // ============= SERVICE TYPE ROUTES =============
  app.get("/api/service-types", isAuthenticated, async (req, res) => {
    try {
      const serviceTypes = await storage.getAllServiceTypes();
      res.json(serviceTypes);
    } catch (error) {
      console.error("Error fetching service types:", error);
      res.status(500).json({ message: "Failed to fetch service types" });
    }
  });

  app.post("/api/service-types", isAuthenticated, async (req, res) => {
    try {
      const serviceType = await storage.createServiceType(req.body);
      res.json(serviceType);
    } catch (error) {
      console.error("Error creating service type:", error);
      res.status(500).json({ message: "Failed to create service type" });
    }
  });

  app.patch("/api/service-types/:id", isAuthenticated, async (req, res) => {
    try {
      const serviceType = await storage.updateServiceType(req.params.id, req.body);
      if (!serviceType) {
        return res.status(404).json({ message: "Service type not found" });
      }
      res.json(serviceType);
    } catch (error) {
      console.error("Error updating service type:", error);
      res.status(500).json({ message: "Failed to update service type" });
    }
  });

  app.delete("/api/service-types/:id", isAuthenticated, async (req, res) => {
    try {
      const serviceType = await storage.deleteServiceType(req.params.id);
      if (!serviceType) {
        return res.status(404).json({ message: "Service type not found" });
      }
      res.json(serviceType);
    } catch (error) {
      console.error("Error deleting service type:", error);
      res.status(500).json({ message: "Failed to delete service type" });
    }
  });

  // ============= CUSTOMER ROUTES =============
  app.get("/api/customers", isAuthenticated, async (req, res) => {
    try {
      const customers = await storage.getAllCustomers();
      res.json(customers);
    } catch (error) {
      console.error("Error fetching customers:", error);
      res.status(500).json({ message: "Failed to fetch customers" });
    }
  });

  app.post("/api/customers", isAuthenticated, async (req, res) => {
    try {
      const validatedData = insertCustomerSchema.parse(req.body);
      const customer = await storage.createCustomer(validatedData);
      res.json(customer);
    } catch (error) {
      if (error instanceof z.ZodError) {
        return res.status(400).json({ 
          message: "Validation failed", 
          errors: fromZodError(error).message 
        });
      }
      console.error("Error creating customer:", error);
      res.status(500).json({ message: "Failed to create customer" });
    }
  });

  app.patch("/api/customers/:id", isAuthenticated, async (req, res) => {
    try {
      const validatedData = insertCustomerSchema.partial().parse(req.body);
      const customer = await storage.updateCustomer(req.params.id, validatedData);
      if (!customer) {
        return res.status(404).json({ message: "Customer not found" });
      }
      res.json(customer);
    } catch (error) {
      if (error instanceof z.ZodError) {
        return res.status(400).json({ 
          message: "Validation failed", 
          errors: fromZodError(error).message 
        });
      }
      console.error("Error updating customer:", error);
      res.status(500).json({ message: "Failed to update customer" });
    }
  });

  app.delete("/api/customers/:id", isAuthenticated, async (req, res) => {
    try {
      const customer = await storage.deleteCustomer(req.params.id);
      if (!customer) {
        return res.status(404).json({ message: "Customer not found" });
      }
      res.json(customer);
    } catch (error) {
      console.error("Error deleting customer:", error);
      res.status(500).json({ message: "Failed to delete customer" });
    }
  });

  // ============= SITE ROUTES =============
  app.get("/api/sites", isAuthenticated, async (req, res) => {
    try {
      const allSites = await storage.getAllSites();
      res.json(allSites);
    } catch (error) {
      console.error("Error fetching sites:", error);
      res.status(500).json({ message: "Failed to fetch sites" });
    }
  });

  app.post("/api/sites", isAuthenticated, async (req, res) => {
    try {
      const site = await storage.createSite(req.body);
      res.json(site);
    } catch (error) {
      console.error("Error creating site:", error);
      res.status(500).json({ message: "Failed to create site" });
    }
  });

  app.patch("/api/sites/:id", isAuthenticated, async (req, res) => {
    try {
      const updated = await storage.updateSite(req.params.id, req.body);
      if (!updated) {
        return res.status(404).json({ message: "Site not found" });
      }
      res.json(updated);
    } catch (error) {
      console.error("Error updating site:", error);
      res.status(500).json({ message: "Failed to update site" });
    }
  });

  app.delete("/api/sites/:id", isAuthenticated, async (req, res) => {
    try {
      const deleted = await storage.deleteSite(req.params.id);
      if (!deleted) {
        return res.status(404).json({ message: "Site not found" });
      }
      res.json({ success: true });
    } catch (error) {
      console.error("Error deleting site:", error);
      res.status(500).json({ message: "Failed to delete site" });
    }
  });

  // ============= SHIFT ROUTES =============
  app.get("/api/shifts", isAuthenticated, async (req, res) => {
    try {
      const allShifts = await storage.getAllShifts();
      
      // Fetch related data for each shift
      const shiftsWithDetails = await Promise.all(
        allShifts.map(async (shift) => {
          const site = await storage.getSite(shift.siteId);
          const assignments = await storage.getShiftAssignments(shift.id);
          
          // Fetch guard details for each assignment
          const assignmentsWithGuards = await Promise.all(
            assignments.map(async (assignment) => {
              const guard = await storage.getGuard(assignment.guardId);
              const certs = guard ? await storage.getCertificationsByGuard(guard.id) : [];
              const user = guard?.userId ? await storage.getUser(guard.userId) : undefined;
              
              return {
                ...assignment,
                guard: guard ? {
                  ...guard,
                  certifications: certs,
                  user,
                } : null,
              };
            })
          );
          
          return {
            ...shift,
            site: site!,
            assignments: assignmentsWithGuards.filter(a => a.guard !== null),
          };
        })
      );

      res.json(shiftsWithDetails);
    } catch (error) {
      console.error("Error fetching shifts:", error);
      res.status(500).json({ message: "Failed to fetch shifts" });
    }
  });

  app.get("/api/shifts/active", isAuthenticated, async (req, res) => {
    try {
      const activeShifts = await storage.getActiveShifts();
      
      // Fetch related data for each shift
      const shiftsWithDetails = await Promise.all(
        activeShifts.map(async (shift) => {
          const site = await storage.getSite(shift.siteId);
          const assignments = await storage.getShiftAssignments(shift.id);
          
          // Fetch guard details for each assignment
          const assignmentsWithGuards = await Promise.all(
            assignments.map(async (assignment) => {
              const guard = await storage.getGuard(assignment.guardId);
              const certs = guard ? await storage.getCertificationsByGuard(guard.id) : [];
              const user = guard?.userId ? await storage.getUser(guard.userId) : undefined;
              
              return {
                ...assignment,
                guard: guard ? {
                  ...guard,
                  certifications: certs,
                  user,
                } : null,
              };
            })
          );
          
          return {
            ...shift,
            site: site!,
            assignments: assignmentsWithGuards.filter(a => a.guard !== null),
          };
        })
      );

      res.json(shiftsWithDetails);
    } catch (error) {
      console.error("Error fetching active shifts:", error);
      res.status(500).json({ message: "Failed to fetch active shifts" });
    }
  });

  app.post("/api/shifts", isAuthenticated, async (req, res) => {
    try {
      // Validate that required fields are present and dates are valid strings
      if (!req.body.siteId || !req.body.startTime || !req.body.endTime) {
        return res.status(400).json({ message: "Missing required fields" });
      }

      // Verifica stato contratto del sito
      const site = await storage.getSite(req.body.siteId);
      if (!site) {
        return res.status(404).json({ message: "Sito non trovato" });
      }

      // Controllo validità contratto - richiesto per creare turni
      if (!site.contractStartDate || !site.contractEndDate) {
        return res.status(400).json({ 
          message: `Impossibile creare turno: il sito "${site.name}" non ha un contratto attivo` 
        });
      }

      // Convert and validate shift dates first
      const startTime = new Date(req.body.startTime);
      const endTime = new Date(req.body.endTime);

      if (isNaN(startTime.getTime()) || isNaN(endTime.getTime())) {
        return res.status(400).json({ message: "Invalid date format" });
      }

      // Normalizza date contratto a giorno intero (00:00 - 23:59)
      const contractStart = new Date(site.contractStartDate);
      contractStart.setHours(0, 0, 0, 0);
      
      const contractEnd = new Date(site.contractEndDate);
      contractEnd.setHours(23, 59, 59, 999);

      // Normalizza data turno a giorno (per confronto)
      const shiftDate = new Date(startTime);
      shiftDate.setHours(0, 0, 0, 0);

      // Verifica che il turno sia dentro il periodo contrattuale
      if (shiftDate > contractEnd) {
        return res.status(400).json({ 
          message: `Impossibile creare turno: il contratto per il sito "${site.name}" scade il ${new Date(site.contractEndDate).toLocaleDateString('it-IT')}` 
        });
      }

      if (shiftDate < contractStart) {
        return res.status(400).json({ 
          message: `Impossibile creare turno: il contratto per il sito "${site.name}" inizia il ${new Date(site.contractStartDate).toLocaleDateString('it-IT')}` 
        });
      }

      // Validate and transform the request body
      const validatedData = insertShiftSchema.parse({
        siteId: req.body.siteId,
        startTime,
        endTime,
        status: req.body.status || "planned",
        vehicleId: req.body.vehicleId || null,
      });
      
      const shift = await storage.createShift(validatedData);

      // Se ci sono guardie da assegnare, crea le assegnazioni
      if (req.body.guardIds && Array.isArray(req.body.guardIds) && req.body.guardIds.length > 0) {
        for (const guardId of req.body.guardIds) {
          await storage.createShiftAssignment({
            shiftId: shift.id,
            guardId,
          });
        }
      }

      res.json(shift);
    } catch (error) {
      console.error("Error creating shift:", error);
      res.status(500).json({ message: "Failed to create shift" });
    }
  });

  app.patch("/api/shifts/:id/status", isAuthenticated, async (req, res) => {
    try {
      await storage.updateShiftStatus(req.params.id, req.body.status);
      res.json({ success: true });
    } catch (error) {
      console.error("Error updating shift status:", error);
      res.status(500).json({ message: "Failed to update shift status" });
    }
  });

  app.patch("/api/shifts/:id", isAuthenticated, async (req, res) => {
    try {
      const { startTime: startTimeStr, endTime: endTimeStr, ...rest } = req.body;
      const updateData: any = { ...rest };

      if (startTimeStr) {
        const startTime = new Date(startTimeStr);
        if (isNaN(startTime.getTime())) {
          return res.status(400).json({ message: "Invalid start time format" });
        }
        updateData.startTime = startTime;
      }

      if (endTimeStr) {
        const endTime = new Date(endTimeStr);
        if (isNaN(endTime.getTime())) {
          return res.status(400).json({ message: "Invalid end time format" });
        }
        updateData.endTime = endTime;
      }

      const updated = await storage.updateShift(req.params.id, updateData);
      if (!updated) {
        return res.status(404).json({ message: "Shift not found" });
      }
      res.json(updated);
    } catch (error) {
      console.error("Error updating shift:", error);
      res.status(500).json({ message: "Failed to update shift" });
    }
  });

  app.delete("/api/shifts/:id", isAuthenticated, async (req, res) => {
    try {
      const deleted = await storage.deleteShift(req.params.id);
      if (!deleted) {
        return res.status(404).json({ message: "Shift not found" });
      }
      res.json({ success: true });
    } catch (error) {
      console.error("Error deleting shift:", error);
      res.status(500).json({ message: "Failed to delete shift" });
    }
  });

  // ============= CCNL VALIDATION =============
  app.post("/api/shifts/validate-ccnl", isAuthenticated, async (req, res) => {
    try {
      const { guardId, shiftStartTime, shiftEndTime } = req.body;
      
      if (!guardId || !shiftStartTime || !shiftEndTime) {
        return res.status(400).json({ message: "Missing required fields" });
      }

      const startTime = new Date(shiftStartTime);
      const endTime = new Date(shiftEndTime);
      
      // Load CCNL parameters
      const params = await db.select().from(contractParameters).limit(1);
      if (params.length === 0) {
        return res.status(500).json({ message: "CCNL parameters not found" });
      }
      const ccnl = params[0];
      
      const violations: Array<{type: string, message: string}> = [];
      
      // Calculate shift hours precisely (in minutes, then convert)
      const shiftMinutes = differenceInMinutes(endTime, startTime);
      const shiftHours = Math.round((shiftMinutes / 60) * 100) / 100; // Round to 2 decimals
      
      // Check max daily hours
      if (shiftHours > ccnl.maxHoursPerDay) {
        violations.push({
          type: "MAX_DAILY_HOURS",
          message: `Turno di ${shiftHours}h supera il limite giornaliero di ${ccnl.maxHoursPerDay}h`
        });
      }
      
      // Get guard's shifts for the week (overlapping with week window)
      const weekStart = startOfWeek(startTime, { weekStartsOn: 1 }); // Monday
      const weekEnd = endOfWeek(startTime, { weekStartsOn: 1 });
      
      const weekShifts = await db
        .select()
        .from(shiftAssignments)
        .innerJoin(shifts, eq(shifts.id, shiftAssignments.shiftId))
        .where(
          and(
            eq(shiftAssignments.guardId, guardId),
            // Shift overlaps week if: (shift_start <= week_end) AND (shift_end >= week_start)
            lte(shifts.startTime, weekEnd),
            gte(shifts.endTime, weekStart)
          )
        );
      
      // Calculate weekly hours precisely (only overlap with week window)
      let weeklyMinutes = 0;
      
      // Add overlap of new shift with week (clamp to 0 if outside window)
      const newShiftStart = startTime < weekStart ? weekStart : startTime;
      const newShiftEnd = endTime > weekEnd ? weekEnd : endTime;
      weeklyMinutes += Math.max(0, differenceInMinutes(newShiftEnd, newShiftStart));
      
      // Add overlap of existing shifts with week (clamp to 0 if outside window)
      for (const { shifts: shift } of weekShifts) {
        const shiftStart = shift.startTime < weekStart ? weekStart : shift.startTime;
        const shiftEnd = shift.endTime > weekEnd ? weekEnd : shift.endTime;
        weeklyMinutes += Math.max(0, differenceInMinutes(shiftEnd, shiftStart));
      }
      const weeklyHours = Math.round((weeklyMinutes / 60) * 100) / 100;
      
      if (weeklyHours > ccnl.maxHoursPerWeek) {
        violations.push({
          type: "MAX_WEEKLY_HOURS",
          message: `Ore settimanali (${weeklyHours}h) superano il limite di ${ccnl.maxHoursPerWeek}h`
        });
      }
      
      // Check consecutive days (chronological order ASC, only past shifts before this one)
      const pastShifts = await db
        .select()
        .from(shiftAssignments)
        .innerJoin(shifts, eq(shifts.id, shiftAssignments.shiftId))
        .where(
          and(
            eq(shiftAssignments.guardId, guardId),
            lte(shifts.startTime, startTime)
          )
        )
        .orderBy(asc(shifts.startTime));
      
      // Build consecutive days map
      const shiftDays = new Set<string>();
      for (const { shifts: shift } of pastShifts) {
        shiftDays.add(startOfDay(shift.startTime).toISOString());
      }
      shiftDays.add(startOfDay(startTime).toISOString());
      
      // Count consecutive days backwards from new shift
      let consecutiveDays = 0;
      let checkDate = startOfDay(startTime);
      while (shiftDays.has(checkDate.toISOString())) {
        consecutiveDays++;
        checkDate = new Date(checkDate);
        checkDate.setDate(checkDate.getDate() - 1);
      }
      
      if (consecutiveDays > 6) {
        violations.push({
          type: "MAX_CONSECUTIVE_DAYS",
          message: `${consecutiveDays} giorni consecutivi superano il limite di 6 giorni`
        });
      }
      
      // Check for overlapping shifts (guard already assigned to another shift at same time)
      // Overlap if: (existing_start < new_end) AND (existing_end > new_start)
      // Note: Use strict inequalities to allow back-to-back shifts (end == start is OK)
      const allAssignments = await db
        .select()
        .from(shiftAssignments)
        .innerJoin(shifts, eq(shifts.id, shiftAssignments.shiftId))
        .where(eq(shiftAssignments.guardId, guardId));
      
      const overlappingShifts = allAssignments.filter(({ shifts: shift }: any) => {
        return shift.startTime < endTime && shift.endTime > startTime;
      });
      
      if (overlappingShifts.length > 0) {
        violations.push({
          type: "OVERLAPPING_SHIFT",
          message: `Guardia già assegnata a un turno sovrapposto in questo orario`
        });
      }
      
      // Check rest hours (only last shift that ended before this one)
      const lastCompletedShifts = await db
        .select()
        .from(shiftAssignments)
        .innerJoin(shifts, eq(shifts.id, shiftAssignments.shiftId))
        .where(
          and(
            eq(shiftAssignments.guardId, guardId),
            lte(shifts.endTime, startTime)
          )
        )
        .orderBy(desc(shifts.endTime))
        .limit(1);
      
      if (lastCompletedShifts.length > 0) {
        const lastShift = lastCompletedShifts[0].shifts;
        const restMinutes = differenceInMinutes(startTime, lastShift.endTime);
        const restHours = Math.round((restMinutes / 60) * 100) / 100;
        
        if (restHours < ccnl.minDailyRestHours) {
          violations.push({
            type: "MIN_REST_HOURS",
            message: `Riposo di ${restHours}h inferiore al minimo di ${ccnl.minDailyRestHours}h`
          });
        }
      }
      
      res.json({ violations, ccnlParams: ccnl });
    } catch (error) {
      console.error("Error validating CCNL:", error);
      res.status(500).json({ message: "Failed to validate CCNL" });
    }
  });

  // ============= SHIFT ASSIGNMENT ROUTES =============
  app.post("/api/shift-assignments", isAuthenticated, async (req, res) => {
    try {
      const assignment = await storage.createShiftAssignment(req.body);
      res.json(assignment);
    } catch (error) {
      console.error("Error creating shift assignment:", error);
      res.status(500).json({ message: "Failed to create shift assignment" });
    }
  });

  app.delete("/api/shift-assignments/:id", isAuthenticated, async (req, res) => {
    try {
      await storage.deleteShiftAssignment(req.params.id);
      res.json({ success: true });
    } catch (error) {
      console.error("Error deleting shift assignment:", error);
      res.status(500).json({ message: "Failed to delete shift assignment" });
    }
  });

  // Create shift assignment with planned time slots
  app.post("/api/shifts/:shiftId/assignments", isAuthenticated, async (req, res) => {
    try {
      const { shiftId } = req.params;
      const { guardId, plannedStartTime, plannedEndTime } = req.body;
      
      if (!guardId || !plannedStartTime || !plannedEndTime) {
        return res.status(400).json({ 
          message: "Missing required fields: guardId, plannedStartTime, plannedEndTime" 
        });
      }
      
      // Validate times
      const startDate = new Date(plannedStartTime);
      const endDate = new Date(plannedEndTime);
      
      if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
        return res.status(400).json({ message: "Invalid date format for plannedStartTime or plannedEndTime" });
      }
      
      if (endDate <= startDate) {
        return res.status(400).json({ message: "plannedEndTime must be after plannedStartTime" });
      }
      
      // Create assignment
      const assignment = await storage.createShiftAssignment({
        shiftId,
        guardId,
        plannedStartTime: startDate,
        plannedEndTime: endDate,
      });
      
      res.json(assignment);
    } catch (error: any) {
      console.error("Error creating shift assignment with time slot:", error);
      if (error.message?.includes('overlap') || error.message?.includes('conflict')) {
        return res.status(409).json({ message: error.message });
      }
      res.status(500).json({ message: "Failed to create shift assignment" });
    }
  });

  // ============= NOTIFICATION ROUTES =============
  app.get("/api/notifications", isAuthenticated, async (req: any, res) => {
    try {
      const userId = getUserId(req);
      const userNotifications = await storage.getNotificationsByUser(userId);
      res.json(userNotifications);
    } catch (error) {
      console.error("Error fetching user notifications:", error);
      res.status(500).json({ message: "Failed to fetch notifications" });
    }
  });

  app.patch("/api/notifications/:id/read", isAuthenticated, async (req, res) => {
    try {
      await storage.markNotificationAsRead(req.params.id);
      res.json({ success: true });
    } catch (error) {
      console.error("Error marking notification as read:", error);
      res.status(500).json({ message: "Failed to mark notification as read" });
    }
  });

  // ============= GUARD CONSTRAINTS ROUTES =============
  app.get("/api/guard-constraints/:guardId", isAuthenticated, async (req, res) => {
    try {
      const constraints = await storage.getGuardConstraints(req.params.guardId);
      res.json(constraints || null);
    } catch (error) {
      console.error("Error fetching guard constraints:", error);
      res.status(500).json({ message: "Failed to fetch guard constraints" });
    }
  });

  app.post("/api/guard-constraints", isAuthenticated, async (req, res) => {
    try {
      const constraints = await storage.upsertGuardConstraints(req.body);
      res.json(constraints);
    } catch (error) {
      console.error("Error upserting guard constraints:", error);
      res.status(500).json({ message: "Failed to save guard constraints" });
    }
  });

  // ============= SITE PREFERENCES ROUTES =============
  app.get("/api/site-preferences/:siteId", isAuthenticated, async (req, res) => {
    try {
      const preferences = await storage.getSitePreferences(req.params.siteId);
      res.json(preferences);
    } catch (error) {
      console.error("Error fetching site preferences:", error);
      res.status(500).json({ message: "Failed to fetch site preferences" });
    }
  });

  app.post("/api/site-preferences", isAuthenticated, async (req, res) => {
    try {
      const preference = await storage.createSitePreference(req.body);
      res.json(preference);
    } catch (error) {
      console.error("Error creating site preference:", error);
      res.status(500).json({ message: "Failed to create site preference" });
    }
  });

  app.delete("/api/site-preferences/:id", isAuthenticated, async (req, res) => {
    try {
      await storage.deleteSitePreference(req.params.id);
      res.json({ success: true });
    } catch (error) {
      console.error("Error deleting site preference:", error);
      res.status(500).json({ message: "Failed to delete site preference" });
    }
  });

  // ============= TRAINING COURSES ROUTES =============
  app.get("/api/training-courses", isAuthenticated, async (req, res) => {
    try {
      const guardId = req.query.guardId as string | undefined;
      const courses = guardId 
        ? await storage.getTrainingCoursesByGuard(guardId)
        : await storage.getAllTrainingCourses();
      res.json(courses);
    } catch (error) {
      console.error("Error fetching training courses:", error);
      res.status(500).json({ message: "Failed to fetch training courses" });
    }
  });

  app.post("/api/training-courses", isAuthenticated, async (req, res) => {
    try {
      const course = await storage.createTrainingCourse(req.body);
      res.json(course);
    } catch (error) {
      console.error("Error creating training course:", error);
      res.status(500).json({ message: "Failed to create training course" });
    }
  });

  app.patch("/api/training-courses/:id", isAuthenticated, async (req, res) => {
    try {
      const updated = await storage.updateTrainingCourse(req.params.id, req.body);
      if (!updated) {
        return res.status(404).json({ message: "Training course not found" });
      }
      res.json(updated);
    } catch (error) {
      console.error("Error updating training course:", error);
      res.status(500).json({ message: "Failed to update training course" });
    }
  });

  app.delete("/api/training-courses/:id", isAuthenticated, async (req, res) => {
    try {
      await storage.deleteTrainingCourse(req.params.id);
      res.json({ success: true });
    } catch (error) {
      console.error("Error deleting training course:", error);
      res.status(500).json({ message: "Failed to delete training course" });
    }
  });

  // ============= HOLIDAYS ROUTES =============
  app.get("/api/holidays", isAuthenticated, async (req, res) => {
    try {
      const year = req.query.year ? parseInt(req.query.year as string) : undefined;
      const holidays = await storage.getAllHolidays(year);
      res.json(holidays);
    } catch (error) {
      console.error("Error fetching holidays:", error);
      res.status(500).json({ message: "Failed to fetch holidays" });
    }
  });

  app.post("/api/holidays", isAuthenticated, async (req, res) => {
    try {
      const holiday = await storage.createHoliday(req.body);
      res.json(holiday);
    } catch (error) {
      console.error("Error creating holiday:", error);
      res.status(500).json({ message: "Failed to create holiday" });
    }
  });

  app.delete("/api/holidays/:id", isAuthenticated, async (req, res) => {
    try {
      await storage.deleteHoliday(req.params.id);
      res.json({ success: true });
    } catch (error) {
      console.error("Error deleting holiday:", error);
      res.status(500).json({ message: "Failed to delete holiday" });
    }
  });

  // ============= ABSENCES ROUTES =============
  app.get("/api/absences", isAuthenticated, async (req, res) => {
    try {
      const guardId = req.query.guardId as string | undefined;
      const absences = guardId
        ? await storage.getAbsencesByGuard(guardId)
        : await storage.getAllAbsences();
      res.json(absences);
    } catch (error) {
      console.error("Error fetching absences:", error);
      res.status(500).json({ message: "Failed to fetch absences" });
    }
  });

  app.post("/api/absences", isAuthenticated, async (req, res) => {
    try {
      const absence = await storage.createAbsence(req.body);
      res.json(absence);
    } catch (error) {
      console.error("Error creating absence:", error);
      res.status(500).json({ message: "Failed to create absence" });
    }
  });

  app.patch("/api/absences/:id", isAuthenticated, async (req, res) => {
    try {
      const updated = await storage.updateAbsence(req.params.id, req.body);
      if (!updated) {
        return res.status(404).json({ message: "Absence not found" });
      }
      res.json(updated);
    } catch (error) {
      console.error("Error updating absence:", error);
      res.status(500).json({ message: "Failed to update absence" });
    }
  });

  app.delete("/api/absences/:id", isAuthenticated, async (req, res) => {
    try {
      await storage.deleteAbsence(req.params.id);
      res.json({ success: true });
    } catch (error) {
      console.error("Error deleting absence:", error);
      res.status(500).json({ message: "Failed to delete absence" });
    }
  });

  // ============= DEV UTILITIES (Reset & Seed Data) =============
  
  // DELETE all sites and guards (for testing)
  app.delete("/api/dev/reset-data", isAuthenticated, async (req, res) => {
    try {
      // Delete all shift assignments first (foreign key constraints)
      await db.delete(shiftAssignments);
      
      // Delete all shifts
      await db.delete(shifts);
      
      // Delete all sites
      await db.delete(sites);
      
      // Delete all certifications
      await db.delete(certifications);
      
      // Delete all guards
      await db.delete(guards);
      
      // Delete all vehicles
      await db.delete(vehicles);
      
      res.json({ 
        success: true, 
        message: "Tutti i dati (siti, guardie, turni, veicoli) sono stati eliminati" 
      });
    } catch (error) {
      console.error("Error resetting data:", error);
      res.status(500).json({ message: "Errore durante reset dati" });
    }
  });
  
  // Create sample data (3 sites Milano + 3 Roccapiemonte, 10 guards each)
  app.post("/api/dev/seed-data", isAuthenticated, async (req, res) => {
    try {
      // Create service types first
      const [serviceTypePresidioFisso] = await db.insert(serviceTypes).values({
        name: "Presidio Fisso",
        description: "Servizio di presidio fisso con guardia armata",
        shiftType: "fixed_post",
        fixedPostHours: 24,
      }).returning();
      
      const [serviceTypePattuglia] = await db.insert(serviceTypes).values({
        name: "Pattuglia Mobile",
        description: "Servizio di pattuglia mobile con passaggi programmati",
        shiftType: "patrol",
        patrolPassages: 4,
      }).returning();
      
      // Create 3 sites for Milano
      const [siteMilano1] = await db.insert(sites).values({
        name: "Banca Centrale Milano",
        address: "Via Dante 45, Milano",
        city: "Milano",
        location: "milano",
        serviceTypeId: serviceTypePresidioFisso.id,
        shiftType: "fixed_post",
        requiresArmed: true,
        requiresDriverLicense: false,
        minGuardsRequired: 1,
        serviceStartTime: "00:00",
        serviceEndTime: "24:00",
        contractReference: "CTR-MI-001-2025",
        contractStartDate: "2025-01-01",
        contractEndDate: "2025-12-31",
      }).returning();
      
      const [siteMilano2] = await db.insert(sites).values({
        name: "Museo Arte Moderna Milano",
        address: "Corso Magenta 12, Milano",
        city: "Milano",
        location: "milano",
        serviceTypeId: serviceTypePattuglia.id,
        shiftType: "patrol",
        requiresArmed: false,
        requiresDriverLicense: true,
        minGuardsRequired: 1,
        serviceStartTime: "08:00",
        serviceEndTime: "20:00",
        contractReference: "CTR-MI-002-2025",
        contractStartDate: "2025-01-01",
        contractEndDate: "2025-06-30",
      }).returning();
      
      const [siteMilano3] = await db.insert(sites).values({
        name: "Centro Commerciale Porta Nuova",
        address: "Piazza Gae Aulenti 1, Milano",
        city: "Milano",
        location: "milano",
        serviceTypeId: serviceTypePresidioFisso.id,
        shiftType: "fixed_post",
        requiresArmed: true,
        requiresDriverLicense: false,
        minGuardsRequired: 2,
        serviceStartTime: "06:00",
        serviceEndTime: "22:00",
        contractReference: "CTR-MI-003-2025",
        contractStartDate: "2025-01-01",
        contractEndDate: "2025-12-31",
      }).returning();
      
      // Create 3 sites for Roccapiemonte
      const [siteRocca1] = await db.insert(sites).values({
        name: "Deposito Logistica Roccapiemonte",
        address: "Via Industriale 23, Roccapiemonte",
        city: "Roccapiemonte",
        location: "roccapiemonte",
        serviceTypeId: serviceTypePresidioFisso.id,
        shiftType: "fixed_post",
        requiresArmed: true,
        requiresDriverLicense: false,
        minGuardsRequired: 1,
        serviceStartTime: "00:00",
        serviceEndTime: "24:00",
        contractReference: "CTR-RC-001-2025",
        contractStartDate: "2025-01-01",
        contractEndDate: "2025-12-31",
      }).returning();
      
      const [siteRocca2] = await db.insert(sites).values({
        name: "Cantiere Edile Salerno Nord",
        address: "SS 18 km 45, Roccapiemonte",
        city: "Roccapiemonte",
        location: "roccapiemonte",
        serviceTypeId: serviceTypePattuglia.id,
        shiftType: "patrol",
        requiresArmed: false,
        requiresDriverLicense: true,
        minGuardsRequired: 1,
        serviceStartTime: "18:00",
        serviceEndTime: "06:00",
        contractReference: "CTR-RC-002-2025",
        contractStartDate: "2025-01-15",
        contractEndDate: "2025-07-15",
      }).returning();
      
      const [siteRocca3] = await db.insert(sites).values({
        name: "Stabilimento Farmaceutico",
        address: "Via delle Industrie 89, Roccapiemonte",
        city: "Roccapiemonte",
        location: "roccapiemonte",
        serviceTypeId: serviceTypePresidioFisso.id,
        shiftType: "fixed_post",
        requiresArmed: true,
        requiresDriverLicense: false,
        minGuardsRequired: 2,
        serviceStartTime: "00:00",
        serviceEndTime: "24:00",
        contractReference: "CTR-RC-003-2025",
        contractStartDate: "2025-01-01",
        contractEndDate: "2025-12-31",
      }).returning();
      
      // Create 10 guards for Milano
      const milanNames = [
        { firstName: "Marco", lastName: "Rossi", badgeNumber: "MI-001" },
        { firstName: "Giulia", lastName: "Bianchi", badgeNumber: "MI-002" },
        { firstName: "Luca", lastName: "Ferrari", badgeNumber: "MI-003" },
        { firstName: "Sara", lastName: "Romano", badgeNumber: "MI-004" },
        { firstName: "Andrea", lastName: "Colombo", badgeNumber: "MI-005" },
        { firstName: "Elena", lastName: "Ricci", badgeNumber: "MI-006" },
        { firstName: "Francesco", lastName: "Marino", badgeNumber: "MI-007" },
        { firstName: "Chiara", lastName: "Greco", badgeNumber: "MI-008" },
        { firstName: "Matteo", lastName: "Bruno", badgeNumber: "MI-009" },
        { firstName: "Alessia", lastName: "Gallo", badgeNumber: "MI-010" },
      ];
      
      for (let i = 0; i < milanNames.length; i++) {
        await db.insert(guards).values({
          ...milanNames[i],
          location: "milano",
          isArmed: i % 2 === 0, // Alternare armati/non armati
          hasDriverLicense: i % 3 === 0, // 1 su 3 con patente
          hasFireSafety: true,
          hasFirstAid: i % 2 === 1,
          phone: `+39 333 ${String(i).padStart(3, '0')}${String(i).padStart(4, '0')}`,
          email: `${milanNames[i].firstName.toLowerCase()}.${milanNames[i].lastName.toLowerCase()}@vigilanza.it`,
        });
      }
      
      // Create 10 guards for Roccapiemonte
      const roccaNames = [
        { firstName: "Antonio", lastName: "Esposito", badgeNumber: "RC-001" },
        { firstName: "Maria", lastName: "De Luca", badgeNumber: "RC-002" },
        { firstName: "Giuseppe", lastName: "Russo", badgeNumber: "RC-003" },
        { firstName: "Anna", lastName: "Costa", badgeNumber: "RC-004" },
        { firstName: "Vincenzo", lastName: "Ferrara", badgeNumber: "RC-005" },
        { firstName: "Rosa", lastName: "Gatti", badgeNumber: "RC-006" },
        { firstName: "Salvatore", lastName: "Leone", badgeNumber: "RC-007" },
        { firstName: "Lucia", lastName: "Longo", badgeNumber: "RC-008" },
        { firstName: "Michele", lastName: "Martino", badgeNumber: "RC-009" },
        { firstName: "Carmela", lastName: "Moretti", badgeNumber: "RC-010" },
      ];
      
      for (let i = 0; i < roccaNames.length; i++) {
        await db.insert(guards).values({
          ...roccaNames[i],
          location: "roccapiemonte",
          isArmed: i % 2 === 0,
          hasDriverLicense: i % 3 === 0,
          hasFireSafety: true,
          hasFirstAid: i % 2 === 1,
          phone: `+39 333 ${String(i + 10).padStart(3, '0')}${String(i + 10).padStart(4, '0')}`,
          email: `${roccaNames[i].firstName.toLowerCase()}.${roccaNames[i].lastName.toLowerCase()}@vigilanza.it`,
        });
      }
      
      // Create 5 vehicles for Milano
      const vehiclesMilano = [
        { licensePlate: "MI123AB", brand: "Fiat", model: "Ducato", vehicleType: "van" as const },
        { licensePlate: "MI456CD", brand: "Volkswagen", model: "Transporter", vehicleType: "van" as const },
        { licensePlate: "MI789EF", brand: "Ford", model: "Transit", vehicleType: "van" as const },
        { licensePlate: "MI012GH", brand: "Renault", model: "Kangoo", vehicleType: "car" as const },
        { licensePlate: "MI345IJ", brand: "Opel", model: "Vivaro", vehicleType: "van" as const },
      ];
      
      for (const vehicle of vehiclesMilano) {
        await db.insert(vehicles).values({
          ...vehicle,
          location: "milano",
          year: 2022,
          status: "available",
        });
      }
      
      // Create 5 vehicles for Roccapiemonte
      const vehiclesRocca = [
        { licensePlate: "SA123AB", brand: "Fiat", model: "Ducato", vehicleType: "van" as const },
        { licensePlate: "SA456CD", brand: "Volkswagen", model: "Caddy", vehicleType: "car" as const },
        { licensePlate: "SA789EF", brand: "Ford", model: "Transit", vehicleType: "van" as const },
        { licensePlate: "SA012GH", brand: "Renault", model: "Master", vehicleType: "van" as const },
        { licensePlate: "SA345IJ", brand: "Peugeot", model: "Partner", vehicleType: "car" as const },
      ];
      
      for (const vehicle of vehiclesRocca) {
        await db.insert(vehicles).values({
          ...vehicle,
          location: "roccapiemonte",
          year: 2023,
          status: "available",
        });
      }
      
      res.json({
        success: true,
        message: "Dati di esempio creati con successo",
        summary: {
          sites: {
            milano: 3,
            roccapiemonte: 3,
          },
          guards: {
            milano: 10,
            roccapiemonte: 10,
          },
          vehicles: {
            milano: 5,
            roccapiemonte: 5,
          },
        },
      });
    } catch (error) {
      console.error("Error seeding data:", error);
      res.status(500).json({ message: "Errore durante creazione dati di esempio" });
    }
  });

  const httpServer = createServer(app);
  return httpServer;
}
