import { FastifyInstance } from 'fastify';
import bcrypt from 'bcrypt';
import crypto from 'crypto';
import {and, eq, inArray, not} from 'drizzle-orm';
import * as schema from '../../db/schema';
import {useNextNumberRangeNumber} from "../utils/functions"; // Pfad anpassen


export const publicLinkService = {

    /**
     * Erstellt einen neuen Public Link
     */
    async createLink(server: FastifyInstance, tenantId: number,
                     name: string,
                     isProtected?: boolean,
                     pin?: string,
                     customToken?: string,
                     config?: Record<string, any>,
                     defaultProfileId?: string) {
        let pinHash: string | null = null;

        // 1. PIN Hashen, falls Schutz aktiviert ist
        if (isProtected && pin) {
            pinHash = await bcrypt.hash(pin, 10);
        } else if (isProtected && !pin) {
            throw new Error("Für geschützte Links muss eine PIN angegeben werden.");
        }

        // 2. Token generieren oder Custom Token verwenden
        let token = customToken;

        if (!token) {
            // Generiere einen zufälligen Token (z.B. hex string)
            // Alternativ: nanoid nutzen, falls installiert
            token = crypto.randomBytes(12).toString('hex');
        }

        // Prüfen, ob Token schon existiert (nur bei Custom Token wichtig)
        if (customToken) {
            const existing = await server.db.query.publicLinks.findFirst({
                where: eq(schema.publicLinks.token, token)
            });
            if (existing) {
                throw new Error(`Der Token '${token}' ist bereits vergeben.`);
            }
        }

        // 3. DB Insert
        const [newLink] = await server.db.insert(schema.publicLinks).values({
            tenant: tenantId,
            name: name,
            token: token,
            isProtected: isProtected || false,
            pinHash: pinHash,
            config: config || {},
            defaultProfile: defaultProfileId || null,
            active: true
        }).returning();

        return newLink;
    },

    /**
     * Listet alle Links für einen Tenant auf (für die Verwaltungs-UI später)
     */
    async getLinksByTenant(server: FastifyInstance, tenantId: number) {
        return server.db.select()
            .from(schema.publicLinks)
            .where(eq(schema.publicLinks.tenant, tenantId));
    },


    async getLinkContext(server: FastifyInstance, token: string, providedPin?: string) {
        // 1. Link laden & Checks
        const linkConfig = await server.db.query.publicLinks.findFirst({
            where: eq(schema.publicLinks.token, token)
        });

        if (!linkConfig || !linkConfig.active) throw new Error("Link_NotFound");

        // 2. PIN Check
        if (linkConfig.isProtected) {
            if (!providedPin) throw new Error("Pin_Required");
            const isValid = await bcrypt.compare(providedPin, linkConfig.pinHash || "");
            if (!isValid) throw new Error("Pin_Invalid");
        }

        const tenantId = linkConfig.tenant;
        const config = linkConfig.config as any;

        // --- RESSOURCEN & FILTER ---

        // Standardmäßig alles laden, wenn 'resources' nicht definiert ist
        const requestedResources: string[] = config.resources || ['profiles', 'projects', 'services', 'units'];
        const filters = config.filters || {}; // Erwartet jetzt: { projects: [1,2], services: [3,4] }

        const queryPromises: Record<string, Promise<any[]>> = {};

        // ---------------------------------------------------------
        // 1. PROFILES (Mitarbeiter)
        // ---------------------------------------------------------
        if (requestedResources.includes('profiles')) {
            let profileCondition = and(
                eq(schema.authProfiles.tenant_id, tenantId),
                eq(schema.authProfiles.active, true)
            );

            // Sicherheits-Feature: Default Profil erzwingen
            if (linkConfig.defaultProfile) {
                profileCondition = and(profileCondition, eq(schema.authProfiles.id, linkConfig.defaultProfile));
            }

            // Optional: Auch hier Filter ermöglichen (falls man z.B. nur 3 bestimmte MA zur Auswahl geben will)
            if (filters.profiles && Array.isArray(filters.profiles) && filters.profiles.length > 0) {
                profileCondition = and(profileCondition, inArray(schema.authProfiles.id, filters.profiles));
            }

            queryPromises.profiles = server.db.select({
                id: schema.authProfiles.id,
                fullName: schema.authProfiles.full_name
            })
                .from(schema.authProfiles)
                .where(profileCondition);
        }

        // ---------------------------------------------------------
        // 2. PROJECTS (Aufträge)
        // ---------------------------------------------------------
        if (requestedResources.includes('projects')) {
            let projectCondition = and(
                eq(schema.projects.tenant, tenantId),
                not(eq(schema.projects.active_phase, 'Abgeschlossen'))
            );

            // NEU: Zugriff direkt auf filters.projects
            if (filters.projects && Array.isArray(filters.projects) && filters.projects.length > 0) {
                projectCondition = and(projectCondition, inArray(schema.projects.id, filters.projects));
            }

            queryPromises.projects = server.db.select({
                id: schema.projects.id,
                name: schema.projects.name
            })
                .from(schema.projects)
                .where(projectCondition);
        }

        // ---------------------------------------------------------
        // 3. SERVICES (Tätigkeiten)
        // ---------------------------------------------------------
        if (requestedResources.includes('services')) {
            let serviceCondition = eq(schema.services.tenant, tenantId);

            // NEU: Zugriff direkt auf filters.services
            if (filters.services && Array.isArray(filters.services) && filters.services.length > 0) {
                serviceCondition = and(serviceCondition, inArray(schema.services.id, filters.services));
            }

            queryPromises.services = server.db.select({
                id: schema.services.id,
                name: schema.services.name,
                unit: schema.services.unit
            })
                .from(schema.services)
                .where(serviceCondition);
        }

        // ---------------------------------------------------------
        // 4. UNITS (Einheiten)
        // ---------------------------------------------------------
        if (requestedResources.includes('units')) {
            // Units werden meist global geladen, könnten aber auch gefiltert werden
            queryPromises.units = server.db.select().from(schema.units);
        }

        // --- QUERY AUSFÜHRUNG ---
        const results = await Promise.all(Object.values(queryPromises));
        const keys = Object.keys(queryPromises);

        const dataResponse: Record<string, any[]> = {
            profiles: [],
            projects: [],
            services: [],
            units: []
        };

        keys.forEach((key, index) => {
            dataResponse[key] = results[index];
        });

        return {
            config: linkConfig.config,
            meta: {
                formName: linkConfig.name,
                defaultProfileId: linkConfig.defaultProfile
            },
            data: dataResponse
        };
    },

    async submitFormData(server: FastifyInstance, token: string, payload: any, providedPin?: string) {
        // 1. Validierung (Token & PIN)
        const linkConfig = await server.db.query.publicLinks.findFirst({
            where: eq(schema.publicLinks.token, token)
        });

        if (!linkConfig || !linkConfig.active) throw new Error("Link_NotFound");

        if (linkConfig.isProtected) {
            if (!providedPin) throw new Error("Pin_Required");
            const isValid = await bcrypt.compare(providedPin, linkConfig.pinHash || "");
            if (!isValid) throw new Error("Pin_Invalid");
        }

        const tenantId = linkConfig.tenant;
        const config = linkConfig.config as any;

        // 2. USER ID AUFLÖSEN
        // Wir holen die profileId aus dem Link (Default) oder dem Payload (User-Auswahl)
        const rawProfileId = linkConfig.defaultProfile || payload.profile;
        if (!rawProfileId) throw new Error("Profile_Missing");

        // Profil laden, um die user_id zu bekommen
        const authProfile = await server.db.query.authProfiles.findFirst({
            where: eq(schema.authProfiles.id, rawProfileId)
        });

        if (!authProfile) throw new Error("Profile_Not_Found");

        // Da du sagtest, es gibt immer einen User, verlassen wir uns darauf.
        // Falls null, werfen wir einen Fehler, da die DB sonst beim Insert knallt.
        const userId = authProfile.user_id;
        if (!userId) throw new Error("Profile_Has_No_User_Assigned");


        // Helper für Datum
        const normalizeDate = (val: any) => {
            if (!val) return null
            const d = new Date(val)
            return isNaN(d.getTime()) ? null : d
        }

        // =================================================================
        // SCHRITT A: Stammdaten laden
        // =================================================================
        const project = await server.db.query.projects.findFirst({
            where: eq(schema.projects.id, payload.project)
        });
        if (!project) throw new Error("Project not found");

        const customer = await server.db.query.customers.findFirst({
            where: eq(schema.customers.id, project.customer)
        });

        const service = await server.db.query.services.findFirst({
            where: eq(schema.services.id, payload.service)
        });
        if (!service) throw new Error("Service not found");

        // Texttemplates & Letterhead laden
        const texttemplates = await server.db.query.texttemplates.findMany({
            where: (t, {and, eq}) => and(
                eq(t.tenant, tenantId),
                eq(t.documentType, 'deliveryNotes')
            )
        });
        const letterhead = await server.db.query.letterheads.findFirst({
            where: eq(schema.letterheads.tenant, tenantId)
        });

        // =================================================================
        // SCHRITT B: Nummernkreis generieren
        // =================================================================
        const {usedNumber} = await useNextNumberRangeNumber(server, tenantId, "deliveryNotes");

        // =================================================================
        // SCHRITT C: Berechnungen
        // =================================================================
        const startDate = normalizeDate(payload.startDate) || new Date();
        let endDate = normalizeDate(payload.endDate);

        // Fallback Endzeit (+1h)
        if (!endDate) {
            endDate = server.dayjs(startDate).add(1, 'hour').toDate();
        }

        // Menge berechnen
        let quantity = payload.quantity;
        if (!quantity && payload.totalHours) quantity = payload.totalHours;
        if (!quantity) {
            const diffMin = server.dayjs(endDate).diff(server.dayjs(startDate), 'minute');
            quantity = Number((diffMin / 60).toFixed(2));
        }

        // =================================================================
        // SCHRITT D: Lieferschein erstellen
        // =================================================================
        const createDocData = {
            tenant: tenantId,
            type: "deliveryNotes",
            state: "Entwurf",
            customer: project.customer,
            //@ts-ignore
            address: customer ? {zip: customer.infoData.zip, city: customer.infoData.city, street: customer.infoData.street,} : {},
            project: project.id,
            documentNumber: usedNumber,
            documentDate: String(new Date()), // Schema sagt 'text', evtl toISOString() besser?
            deliveryDate: String(startDate),  // Schema sagt 'text'
            deliveryDateType: "Leistungsdatum",
            createdBy: userId, // WICHTIG: Hier die User ID
            created_by: userId, // WICHTIG: Hier die User ID
            title: "Lieferschein",
            description: "",
            startText: texttemplates.find((i: any) => i.default && i.pos === "startText")?.text || "",
            endText: texttemplates.find((i: any) => i.default && i.pos === "endText")?.text || "",
            rows: [
                {
                    pos: "1",
                    mode: "service",
                    service: service.id,
                    text: service.name,
                    unit: service.unit,
                    quantity: quantity,
                    description: service.description || null,
                    descriptionText: service.description || null,
                    agriculture: {
                        dieselUsage: payload.dieselUsage || null,
                    }
                }
            ],
            contactPerson: userId, // WICHTIG: Hier die User ID
            letterhead: letterhead?.id,
        };

        const [createdDoc] = await server.db.insert(schema.createddocuments)
            //@ts-ignore
            .values(createDocData)
            .returning();

        // =================================================================
        // SCHRITT E: Zeiterfassung Events
        // =================================================================
        if (config.features?.timeTracking) {

            // Metadaten für das Event
            const eventMetadata = {
                project: project.id,
                service: service.id,
                description: payload.description || "",
                generatedDocumentId: createdDoc.id
            };

            // 1. START EVENT
            await server.db.insert(schema.stafftimeevents).values({
                tenant_id: tenantId,
                user_id: userId,          // WICHTIG: User ID
                actortype: "user",
                actoruser_id: userId,     // WICHTIG: User ID
                eventtime: startDate,
                eventtype: "START",
                source: "PUBLIC_LINK",
                metadata: eventMetadata   // WICHTIG: Schema heißt 'metadata', nicht 'payload'
            });

            // 2. STOP EVENT
            await server.db.insert(schema.stafftimeevents).values({
                tenant_id: tenantId,
                user_id: userId,
                actortype: "user",
                actoruser_id: userId,
                eventtime: endDate,
                eventtype: "STOP",
                source: "PUBLIC_LINK",
                metadata: eventMetadata
            });
        }

        // =================================================================
        // SCHRITT F: History Items
        // =================================================================
        const historyItemsToCreate = [];

        if (payload.description) {
            historyItemsToCreate.push({
                tenant: tenantId,
                createdBy: userId, // WICHTIG: User ID
                text: `Notiz aus Webformular Lieferschein ${createdDoc.documentNumber}: ${payload.description}`,
                project: project.id,
                createdDocument: createdDoc.id
            });
        }

        historyItemsToCreate.push({
            tenant: tenantId,
            createdBy: userId, // WICHTIG: User ID
            text: `Webformular abgeschickt. Lieferschein ${createdDoc.documentNumber} erstellt. Zeit gebucht (Start/Stop).`,
            project: project.id,
            createdDocument: createdDoc.id
        });

        await server.db.insert(schema.historyitems).values(historyItemsToCreate);

        return {success: true, documentNumber: createdDoc.documentNumber};
    }
}