import { FastifyInstance } from "fastify"
import {
    stafftimeentries,
    stafftimenetryconnects
} from "../../../db/schema"
import {
    eq,
    and,
    gte,
    lte,
    desc
} from "drizzle-orm"
import {stafftimeevents} from "../../../db/schema/staff_time_events";
import {compareTimeEvents, loadRelatedAdminEvents, loadValidEvents} from "../../modules/time/loadvalidevents.service";
import {deriveTimeSpans} from "../../modules/time/derivetimespans.service";
import {buildTimeEvaluationFromSpans} from "../../modules/time/buildtimeevaluation.service";
import {z} from "zod";
import {enrichSpansWithStatus} from "../../modules/time/enrichtimespanswithstatus.service";

export default async function staffTimeRoutes(server: FastifyInstance) {


    server.post("/staff/time/event", async (req, reply) => {
        try {
            const actorId = req.user.user_id;
            const tenantId = req.user.tenant_id

            const body = req.body as any

            const normalizeDate = (val: any) => {
                if (!val) return null
                const d = new Date(val)
                return isNaN(d.getTime()) ? null : d
            }

            const dataToInsert = {
                tenant_id: tenantId,
                user_id: body.user_id,
                actortype: "user",
                actoruser_id: actorId,
                eventtime: normalizeDate(body.eventtime),
                eventtype: body.eventtype,
                source: "WEB",
                payload: body.payload // Payload (z.B. Description) mit speichern
            }

            const [created] = await server.db
                .insert(stafftimeevents)
                //@ts-ignore
                .values(dataToInsert)
                .returning()

            return created
        } catch (err: any) {
            console.error(err)
            return reply.code(400).send({ error: err.message })
        }
    })

    // 🆕 POST /staff/time/edit (Bearbeiten durch Invalidieren + Neu erstellen)
    server.post("/staff/time/edit", async (req, reply) => {
        try {
            // 1. Der "Actor" ist der, der gerade eingeloggt ist (z.B. Manager)
            const actorId = req.user.user_id;
            const tenantId = req.user.tenant_id;

            const {
                originalEventIds,
                newStart,
                newEnd,
                newType,
                description,
                reason
            } = req.body as {
                originalEventIds: string[],
                newStart: string,
                newEnd: string | null,
                newType: string,
                description?: string,
                reason?: string
            };

            if (!originalEventIds || originalEventIds.length === 0) {
                return reply.code(400).send({ error: "Keine Events zum Bearbeiten angegeben." });
            }

            // -----------------------------------------------------------
            // SCHRITT A: Den eigentlichen Besitzer (Mitarbeiter) ermitteln
            // -----------------------------------------------------------
            // Wir holen uns das erste Event aus der Liste, um zu sehen, wem es gehört.
            const existingEvents = await server.db
                .select({
                    user_id: stafftimeevents.user_id,
                    tenant_id: stafftimeevents.tenant_id
                })
                .from(stafftimeevents)
                .where(and(
                    eq(stafftimeevents.id, originalEventIds[0]),
                    eq(stafftimeevents.tenant_id, tenantId) // Sicherheitscheck: Nur im eigenen Tenant
                ))
                .limit(1);

            if (existingEvents.length === 0) {
                return reply.code(404).send({ error: "Ursprüngliches Event nicht gefunden oder Zugriff verweigert." });
            }

            // Das ist der Mitarbeiter, dem die Zeit gehört
            const targetUserId = existingEvents[0].user_id;


            // -----------------------------------------------------------
            // SCHRITT B: Transaktion durchführen
            // -----------------------------------------------------------
            await server.db.transaction(async (tx) => {

                // 1. INVALIDIEREN
                // Wir nutzen 'targetUserId' als Besitzer des Events, aber 'actorId' als Auslöser
                const invalidations = originalEventIds.map(id => ({
                    tenant_id: tenantId,
                    user_id: targetUserId,      // <--- WICHTIG: Gehört dem Mitarbeiter
                    actortype: "user",
                    actoruser_id: actorId,      // <--- WICHTIG: Geändert durch Manager/Self
                    eventtime: new Date(),
                    eventtype: "invalidated",
                    source: "WEB",
                    related_event_id: id,
                    invalidates_event_id: id,
                    metadata: {
                        reason: reason || "Bearbeitung",
                        replaced_by_edit: true
                    }
                }));

                // @ts-ignore
                await tx.insert(stafftimeevents).values(invalidations);

                // 2. NEU ERSTELLEN

                // Start Event
                // @ts-ignore
                await tx.insert(stafftimeevents).values({
                    tenant_id: tenantId,
                    user_id: targetUserId,      // <--- Gehört dem Mitarbeiter
                    actortype: "user",
                    actoruser_id: actorId,      // <--- Erstellt durch Manager/Self
                    eventtime: new Date(newStart),
                    eventtype: `${newType}_start`,
                    source: "WEB",
                    payload: { description: description || "" }
                });

                // End Event (nur wenn vorhanden)
                if (newEnd) {
                    // @ts-ignore
                    await tx.insert(stafftimeevents).values({
                        tenant_id: tenantId,
                        user_id: targetUserId,  // <--- Gehört dem Mitarbeiter
                        actortype: "user",
                        actoruser_id: actorId,  // <--- Erstellt durch Manager/Self
                        eventtime: new Date(newEnd),
                        eventtype: `${newType}_end`,
                        source: "WEB"
                    });
                }
            });

            return { success: true };

        } catch (err: any) {
            console.error("Fehler beim Bearbeiten:", err);
            return reply.code(500).send({ error: err.message });
        }
    });

    // POST /staff/time/submit
    server.post("/staff/time/submit", async (req, reply) => {
        try {
            const userId = req.user.user_id; // Mitarbeiter, der einreicht
            const tenantId = req.user.tenant_id;

            // Erwartet eine Liste von IDs der faktischen Events (work_start, work_end, etc.)
            const { eventIds } = req.body as { eventIds: string[] };

            if (eventIds.length === 0) {
                return reply.code(400).send({ error: "Keine Events zum Einreichen angegeben." });
            }

            const inserts = eventIds.map((eventId) => ({
                tenant_id: tenantId,
                user_id: userId, // Event gehört zum Mitarbeiter
                actortype: "user",
                actoruser_id: userId, // Mitarbeiter ist der Akteur
                eventtime: new Date(),
                eventtype: "submitted", // NEU: Event-Typ für Einreichung
                source: "WEB",
                related_event_id: eventId, // Verweis auf das faktische Event
            }));

            const createdEvents = await server.db
                .insert(stafftimeevents)
                //@ts-ignore
                .values(inserts)
                .returning();

            return { submittedCount: createdEvents.length };
        } catch (err: any) {
            console.error(err);
            return reply.code(500).send({ error: err.message });
        }
    });

    // POST /staff/time/approve
    server.post("/staff/time/approve", async (req, reply) => {
        try {
            // 🚨 Berechtigungsprüfung (Voraussetzung: req.user enthält Manager-Status)
            /*if (!req.user.isManager) {
                return reply.code(403).send({ error: "Keine Genehmigungsberechtigung." });
            }*/

            const actorId = req.user.user_id; // Manager ist der Akteur
            const tenantId = req.user.tenant_id;

            const { eventIds, employeeUserId } = req.body as {
                eventIds: string[];
                employeeUserId: string; // Die ID des Mitarbeiters, dessen Zeit genehmigt wird
            };

            if (eventIds.length === 0) {
                return reply.code(400).send({ error: "Keine Events zur Genehmigung angegeben." });
            }

            const inserts = eventIds.map((eventId) => ({
                tenant_id: tenantId,
                user_id: employeeUserId, // Event gehört zum Mitarbeiter
                actortype: "user",
                actoruser_id: actorId, // Manager ist der Akteur
                eventtime: new Date(),
                eventtype: "approved", // NEU: Event-Typ für Genehmigung
                source: "WEB",
                related_event_id: eventId, // Verweis auf das faktische Event
                metadata: {
                    // Optional: Genehmigungskommentar
                    approvedBy: req.user.email
                }
            }));

            const createdEvents = await server.db
                .insert(stafftimeevents)
                //@ts-ignore
                .values(inserts)
                .returning();

            return { approvedCount: createdEvents.length };
        } catch (err: any) {
            console.error(err);
            return reply.code(500).send({ error: err.message });
        }
    });

    // POST /staff/time/reject
    server.post("/staff/time/reject", async (req, reply) => {
        try {
            // 🚨 Berechtigungsprüfung
            /*if (!req.user.isManager) {
                return reply.code(403).send({ error: "Keine Zurückweisungsberechtigung." });
            }*/

            const actorId = req.user.user_id; // Manager ist der Akteur
            const tenantId = req.user.tenant_id;

            const { eventIds, employeeUserId, reason } = req.body as {
                eventIds: string[];
                employeeUserId: string;
                reason?: string; // Optionaler Grund für die Ablehnung
            };

            if (eventIds.length === 0) {
                return reply.code(400).send({ error: "Keine Events zur Ablehnung angegeben." });
            }

            const inserts = eventIds.map((eventId) => ({
                tenant_id: tenantId,
                user_id: employeeUserId, // Event gehört zum Mitarbeiter
                actortype: "user",
                actoruser_id: actorId, // Manager ist der Akteur
                eventtime: new Date(),
                eventtype: "rejected", // NEU: Event-Typ für Ablehnung
                source: "WEB",
                related_event_id: eventId, // Verweis auf das faktische Event
                metadata: {
                    reason: reason || "Ohne Angabe"
                }
            }));

            const createdEvents = await server.db
                .insert(stafftimeevents)
                //@ts-ignore
                .values(inserts)
                .returning();

            return { rejectedCount: createdEvents.length };
        } catch (err: any) {
            console.error(err);
            return reply.code(500).send({ error: err.message });
        }
    });

    // GET /api/staff/time/spans
    server.get("/staff/time/spans", async (req, reply) => {
        try {
            // Der eingeloggte User (Anfragesteller)
            const actingUserId = req.user.user_id;
            const tenantId = req.user.tenant_id;

            // Query-Parameter: targetUserId ist optional
            const { targetUserId } = req.query as { targetUserId?: string };

            // Falls eine targetUserId übergeben wurde, nutzen wir diese, sonst die eigene ID
            const evaluatedUserId = targetUserId || actingUserId;

            // 💡 "Unendlicher" Zeitraum, wie gewünscht
            const startDate = new Date(0); // 1970
            const endDate = new Date("2100-12-31");

            // SCHRITT 1: Lade ALLE Events für den ZIEL-USER (evaluatedUserId)
            // WICHTIG: loadValidEvents muss "invalidated" Events und deren related_ids ausfiltern!
            const allEventsInTimeFrame = await loadValidEvents(
                server,
                tenantId,
                evaluatedUserId, // <--- Hier wird jetzt die variable ID genutzt
                startDate,
                endDate
            );

            // SCHRITT 2: Filtere faktische Events
            const FACTUAL_EVENT_TYPES = new Set([
                "work_start", "work_end", "pause_start", "pause_end",
                "sick_start", "sick_end", "vacation_start", "vacation_end",
                "overtime_compensation_start", "overtime_compensation_end",
                "auto_stop"
            ]);
            const factualEvents = allEventsInTimeFrame.filter(e => FACTUAL_EVENT_TYPES.has(e.eventtype));

            // SCHRITT 3: Hole administrative Events
            const factualEventIds = factualEvents.map(e => e.id);

            if (factualEventIds.length === 0) {
                return [];
            }

            const relatedAdminEvents = await loadRelatedAdminEvents(server, factualEventIds);

            // SCHRITT 4: Kombinieren und Sortieren
            const combinedEvents = [
                ...factualEvents,
                ...relatedAdminEvents,
            ].sort(compareTimeEvents);

            // SCHRITT 5: Spans ableiten
            const derivedSpans = deriveTimeSpans(combinedEvents);

            // SCHRITT 6: Spans anreichern
            const enrichedSpans = enrichSpansWithStatus(derivedSpans, combinedEvents);

            return enrichedSpans;

        } catch (error) {
            console.error("Fehler beim Laden der Spans:", error);
            return reply.code(500).send({ error: "Interner Fehler beim Laden der Zeitspannen." });
        }
    });

    server.get("/staff/time/evaluation", async (req, reply) => {
        try {
            // --- 1. Eingangsdaten und Validierung des aktuellen Nutzers ---

            // Daten des aktuell eingeloggten (anfragenden) Benutzers
            const actingUserId = req.user.user_id;
            const tenantId = req.user.tenant_id;

            // Query-Parameter extrahieren
            const { from, to, targetUserId } = req.query as {
                from: string,
                to: string,
                targetUserId?: string // Optionale ID des Benutzers, dessen Daten abgerufen werden sollen
            };

            // Die ID, für die die Auswertung tatsächlich durchgeführt wird
            const evaluatedUserId = targetUserId || actingUserId;

            const startDate = new Date(from);
            let endDateQuery = new Date(to);
            endDateQuery.setDate(endDateQuery.getDate() + 1);
            const endDate = endDateQuery;

            if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
                return reply.code(400).send({ error: "Ungültiges Datumsformat." });
            }

            // --- 3. Ausführung der Logik für den ermittelten Benutzer ---

            // SCHRITT 1: Lade ALLE gültigen Events im Zeitraum
            // WICHTIG: loadValidEvents muss "invalidated" Events und deren related_ids ausfiltern!
            const allEventsInTimeFrame = await loadValidEvents(
                server, tenantId, evaluatedUserId, startDate, endDate // Verwendung der evaluatedUserId
            );

            // 1b: Trenne Faktische und Administrative Events
            const FACTUAL_EVENT_TYPES = new Set([
                "work_start", "work_end", "pause_start", "pause_end",
                "sick_start", "sick_end", "vacation_start", "vacation_end",
                "overtime_compensation_start", "overtime_compensation_end",
                "auto_stop"
            ]);
            const factualEvents = allEventsInTimeFrame.filter(e => FACTUAL_EVENT_TYPES.has(e.eventtype));

            // 1c: Sammle alle IDs der faktischen Events im Zeitraum
            const factualEventIds = factualEvents.map(e => e.id);

            // SCHRITT 2: Lade die administrativen Events, die sich auf diese IDs beziehen (auch NACH dem Zeitraum)
            const relatedAdminEvents = await loadRelatedAdminEvents(server, factualEventIds);

            // SCHRITT 3: Kombiniere alle Events für die Weiterverarbeitung
            const combinedEvents = [
                ...factualEvents,
                ...relatedAdminEvents,
            ].sort(compareTimeEvents);

            // SCHRITT 4: Ableiten und Anreichern
            const derivedSpans = deriveTimeSpans(combinedEvents);
            const enrichedSpans = enrichSpansWithStatus(derivedSpans, combinedEvents);

            // SCHRITT 5: Erstellung der finalen Auswertung (Summen und Salden)
            const evaluationSummary = await buildTimeEvaluationFromSpans(
                server,
                evaluatedUserId, // Verwendung der evaluatedUserId
                tenantId,
                from,
                to,
                enrichedSpans
            );

            return {
                userId: evaluatedUserId, // Rückgabe der ID, für die ausgewertet wurde
                spans: enrichedSpans,
                summary: evaluationSummary
            };


        } catch (error) {
            console.error("Fehler in /staff/time/evaluation:", error);
            return reply.code(500).send({ error: "Interner Serverfehler bei der Zeitauswertung." });
        }
    });

}
