import { FastifyInstance } from "fastify";
import {createInvoicePDF, createTimeSheetPDF} from "../utils/pdf";
import {encodeBase64ToNiimbot, generateLabel, useNextNumberRangeNumber} from "../utils/functions";
import { GetObjectCommand } from "@aws-sdk/client-s3";
import { execFile } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import path from "node:path";
import { promisify } from "node:util";
import dayjs from "dayjs";
//import { ready as zplReady } from 'zpl-renderer-js'
//import { renderZPL } from "zpl-image";

import customParseFormat from "dayjs/plugin/customParseFormat.js";
import isoWeek from "dayjs/plugin/isoWeek.js";
import isBetween from "dayjs/plugin/isBetween.js";
import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js"
import isSameOrBefore from "dayjs/plugin/isSameOrBefore.js"
import duration from "dayjs/plugin/duration.js";
import timezone from "dayjs/plugin/timezone.js";
import {generateTimesEvaluation} from "../modules/time/evaluation.service";
import {citys, files} from "../../db/schema";
import {and, eq, isNull, not} from "drizzle-orm";
import {executeManualGeneration, finishManualGeneration} from "../modules/serialexecution.service";
import { s3 } from "../utils/s3";
import { secrets } from "../utils/secrets";
import { storeExtractedTextForFile } from "../utils/documentText";
import { generateLiquidityForecast } from "../utils/liquidityForecast";
dayjs.extend(customParseFormat)
dayjs.extend(isoWeek)
dayjs.extend(isBetween)
dayjs.extend(isSameOrAfter)
dayjs.extend(isSameOrBefore)
dayjs.extend(duration)
dayjs.extend(timezone)

const execFileAsync = promisify(execFile)

function resolveGitRoot() {
    const searchRoots = [
        process.cwd(),
        path.resolve(process.cwd(), ".."),
        path.resolve(__dirname, "../../.."),
        path.resolve(__dirname, "../../../.."),
    ]

    for (const startDir of searchRoots) {
        let currentDir = startDir

        while (currentDir && currentDir !== path.dirname(currentDir)) {
            if (existsSync(path.join(currentDir, ".git"))) {
                return currentDir
            }

            currentDir = path.dirname(currentDir)
        }
    }

    return null
}

function getDeploymentChangelogFallback() {
    const backendPackagePath = path.resolve(process.cwd(), "package.json")
    let version = "unbekannt"

    if (existsSync(backendPackagePath)) {
        try {
            const packageJson = JSON.parse(readFileSync(backendPackagePath, "utf-8"))
            version = packageJson?.version || version
        } catch (err) {
            console.error("Could not read backend package.json for changelog fallback", err)
        }
    }

    const commitHash =
        process.env.RAILWAY_GIT_COMMIT_SHA ||
        process.env.VERCEL_GIT_COMMIT_SHA ||
        process.env.GITHUB_SHA ||
        process.env.COMMIT_SHA ||
        process.env.SOURCE_COMMIT ||
        null

    const committedAt =
        process.env.BUILD_DATE ||
        process.env.RENDER_GIT_COMMIT_DATE ||
        process.env.VERCEL_GIT_COMMIT_DATE ||
        new Date().toISOString()

    return [{
        hash: commitHash || `version-${version}`,
        shortHash: commitHash ? commitHash.slice(0, 7) : `v${version}`,
        subject: `Bereitgestellte Version ${version}`,
        authorName: "Deployment",
        committedAt
    }]
}

export default async function functionRoutes(server: FastifyInstance) {
    const streamToBuffer = async (stream: any): Promise<Buffer> =>
        new Promise((resolve, reject) => {
            const chunks: Buffer[] = [];
            stream.on("data", (chunk: Buffer) => chunks.push(chunk));
            stream.on("error", reject);
            stream.on("end", () => resolve(Buffer.concat(chunks)));
        });

    server.post("/functions/pdf/:type", async (req, reply) => {
        const body = req.body as {
            data: any
            backgroundPath?: string
        }
        const {type} = req.params as {type:string}


        try {

            let pdf = null

            if(type === "createdDocument") {
                pdf = await createInvoicePDF(
                    server,
                    "base64",
                    body.data,
                    body.backgroundPath
                )
            } else if(type === "timesheet") {
                pdf = await createTimeSheetPDF(
                    server,
                    "base64",
                    body.data,
                    body.backgroundPath
                )
            }

            return pdf // Fastify wandelt automatisch in JSON
        } catch (err) {
            console.log(err)
            reply.code(500).send({ error: "Failed to create PDF" })
        }
    })

    server.get("/functions/usenextnumber/:numberrange", async (req, reply) => {
            const { numberrange } = req.params as { numberrange: string };
            const tenant = (req as any).user.tenant_id

            try {
                const result = await useNextNumberRangeNumber(server,tenant, numberrange)
                reply.send(result) // JSON automatisch
            } catch (err) {
                req.log.error(err)
                reply.code(500).send({ error: "Failed to generate next number" })
            }
        })

    /**
     * @route GET /functions/workingtimeevaluation/:user_id
     * @query start_date=YYYY-MM-DD
     * @query end_date=YYYY-MM-DD
     */
    server.get("/functions/timeevaluation/:user_id", async (req, reply) => {
        const { user_id } = req.params as { user_id: string }
        const { start_date, end_date } = req.query as { start_date: string; end_date: string }
        const { tenant_id } = req.user

        // 🔒 Sicherheitscheck: andere User nur bei Berechtigung
        if (user_id !== req.user.user_id && !req.hasPermission("staff.time.read_all")) {
            return reply.code(403).send({ error: "Not allowed to view other users." })
        }

        try {
            const result = await generateTimesEvaluation(server, user_id, tenant_id, start_date, end_date)
            reply.send(result)
        } catch (error) {
            console.error(error)
            reply.code(500).send({ error: error.message })
        }
    })

    server.get('/functions/check-zip/:zip', async (req, reply) => {
        const { zip } = req.params as { zip: string }
        const normalizedZip = String(zip || "").replace(/\D/g, "")

        if (normalizedZip.length !== 5) {
            return reply.code(400).send({ error: 'ZIP must contain exactly 5 digits' })
        }

        try {
            const data = await server.db
                .select()
                .from(citys)
                .where(eq(citys.zip, Number(normalizedZip)))


            if (!data.length) {
                return reply.code(404).send({ error: 'ZIP not found' })
            }

            const city = data[0]

            //districtMap
            const bundeslaender = [
                { code: 'DE-BW', name: 'Baden-Württemberg' },
                { code: 'DE-BY', name: 'Bayern' },
                { code: 'DE-BE', name: 'Berlin' },
                { code: 'DE-BB', name: 'Brandenburg' },
                { code: 'DE-HB', name: 'Bremen' },
                { code: 'DE-HH', name: 'Hamburg' },
                { code: 'DE-HE', name: 'Hessen' },
                { code: 'DE-MV', name: 'Mecklenburg-Vorpommern' },
                { code: 'DE-NI', name: 'Niedersachsen' },
                { code: 'DE-NW', name: 'Nordrhein-Westfalen' },
                { code: 'DE-RP', name: 'Rheinland-Pfalz' },
                { code: 'DE-SL', name: 'Saarland' },
                { code: 'DE-SN', name: 'Sachsen' },
                { code: 'DE-ST', name: 'Sachsen-Anhalt' },
                { code: 'DE-SH', name: 'Schleswig-Holstein' },
                { code: 'DE-TH', name: 'Thüringen' }
            ]



            return reply.send({
                ...city,
                state_code: bundeslaender.find(i => i.name === city.countryName)?.code || null
            })
        } catch (err) {
            console.log(err)
            return reply.code(500).send({ error: 'Internal server error' })
        }
    })

    server.get('/functions/changelog', async (req, reply) => {
        const { limit } = req.query as { limit?: string | number }
        const parsedLimit = Number(limit)
        const safeLimit = Number.isFinite(parsedLimit)
            ? Math.min(Math.max(parsedLimit, 1), 50)
            : 15

        const gitRoot = resolveGitRoot()

        if (!gitRoot) {
            return reply.send({
                repositoryRoot: null,
                source: 'deployment',
                entries: getDeploymentChangelogFallback().slice(0, safeLimit)
            })
        }

        try {
            const { stdout } = await execFileAsync('git', [
                '-C',
                gitRoot,
                'log',
                `--max-count=${safeLimit}`,
                '--date=iso-strict',
                '--pretty=format:%H%x1f%h%x1f%s%x1f%an%x1f%aI%x1e'
            ])

            const entries = stdout
                .split('\x1e')
                .map(entry => entry.trim())
                .filter(Boolean)
                .map(entry => {
                    const [hash, shortHash, subject, authorName, committedAt] = entry.split('\x1f')

                    return {
                        hash,
                        shortHash,
                        subject,
                        authorName,
                        committedAt
                    }
                })

            return reply.send({
                repositoryRoot: gitRoot,
                source: 'git',
                entries
            })
        } catch (err) {
            req.log.error(err)
            return reply.send({
                repositoryRoot: gitRoot,
                source: 'deployment',
                entries: getDeploymentChangelogFallback().slice(0, safeLimit)
            })
        }
    })

    server.post('/functions/serial/start', async (req, reply) => {
        console.log(req.body)
        const {executionDate,templateIds,tenantId} = req.body as {executionDate:string,templateIds:Number[],tenantId:Number}
        await executeManualGeneration(server,executionDate,templateIds,tenantId,req.user.user_id)
    })

    server.post('/functions/serial/finish/:execution_id', async (req, reply) => {
       const {execution_id} = req.params as { execution_id: string }
        //@ts-ignore
        await finishManualGeneration(server,execution_id)
    })

    server.post('/functions/services/bankstatementsync', async (req, reply) => {
        await server.services.bankStatements.run(req.user.tenant_id);
    })

    server.post('/functions/services/prepareincominginvoices', async (req, reply) => {

        await server.services.prepareIncomingInvoices.run(req.user.tenant_id)
    })

    server.get('/functions/liquidity-forecast', async (req, reply) => {
        const { ignoredRecurringKeys } = req.query as { ignoredRecurringKeys?: string }
        const ignoredKeys = String(ignoredRecurringKeys || "")
            .split(",")
            .map((key) => key.trim())
            .filter(Boolean)

        try {
            return await generateLiquidityForecast(server, req.user.tenant_id, ignoredKeys)
        } catch (err) {
            req.log.error(err)
            return reply.code(500).send({ error: "Liquiditätsprognose konnte nicht erstellt werden." })
        }
    })

    server.post('/functions/services/backfillfiletext', async (req, reply) => {
        const tenantId = req.user.tenant_id

        const pendingFiles = await server.db
            .select()
            .from(files)
            .where(
                and(
                    eq(files.tenant, tenantId),
                    eq(files.archived, false),
                    not(isNull(files.path)),
                    isNull(files.extractedText)
                )
            )

        let processed = 0
        let withText = 0
        let errors = 0

        for (const file of pendingFiles) {
            try {
                const response: any = await s3.send(new GetObjectCommand({
                    Bucket: secrets.S3_BUCKET,
                    Key: file.path!
                }))

                const fileBuffer = await streamToBuffer(response.Body)
                const result = await storeExtractedTextForFile(
                    server,
                    file.id,
                    fileBuffer,
                    file.mimeType,
                    file.name || file.path?.split("/").pop()
                )

                processed += 1
                if (result.text) withText += 1
            } catch (err) {
                errors += 1
                server.log.error(`Failed to backfill extracted text for file ${file.id}`)
                server.log.error(err)
            }
        }

        return {
            pending: pendingFiles.length,
            processed,
            withText,
            errors
        }
    })

    server.post('/functions/services/syncdokubox', async (req, reply) => {

        await server.services.dokuboxSync.run()
    })

    server.post('/print/label', async (req, reply) => {
        const { context, width = 584, height = 354 } = req.body as {context:any,width:number,height:number}

        try {
            const base64 = await generateLabel(context,width,height)

            return {
                encoded: await encodeBase64ToNiimbot(base64, 'top'),
                base64: base64
            }
        } catch (err) {
            console.error('[Label Render Error]', err)
            return reply.code(500).send({ error: err.message || 'Failed to render label' })
        }
    })

}
