import express, { Request, Response } from "express";
import { getLogin, getTrusted } from "../auth";
import { addChoice, getDateForWeekIndex, getToday, removeChoice, removeChoices, updateDepartureTime, updateNote, fetchRestaurantWeekMenuData, saveRestaurantWeekMenu, updateBuyer } from "../service";
import { getDayOfWeekIndex, parseToken, getFirstWorkDayOfWeek } from "../utils";
import { getWebsocket } from "../websocket";
import { callNotifikace } from "../notifikace";
import { AddChoiceData, ChangeDepartureTimeData, MealSlot, RemoveChoiceData, RemoveChoicesData, UdalostEnum, UpdateNoteData } from "../../../types/gen/types.gen";


// RateLimit na refresh endpoint
interface RateLimitEntry {
    count: number;
    resetTime: number;
}
const rateLimits: Record<string, RateLimitEntry> = {};
const RATE_LIMIT = 1; // maximální počet požadavků za minutu
const RATE_LIMIT_WINDOW = 30 * 60 * 1000; // je to v ms (x * 1min)

// Kontrola ratelimitu
function checkRateLimit(key: string, limit: number = RATE_LIMIT): boolean {
    const now = Date.now();

    // Vyčištění starých záznamů
    Object.keys(rateLimits).forEach(k => {
        if (rateLimits[k].resetTime < now) {
            delete rateLimits[k];
        }
    });

    // Kontrola, že záznam existuje a platí
    if (rateLimits[key] && rateLimits[key].resetTime > now) {
        // Záznam platí a kontroluje se limit
        if (rateLimits[key].count >= limit) {
            return false; // Překročen limit
        }

        // ++ xd
        rateLimits[key].count++;
        return true;
    } else {
        // + klic
        rateLimits[key] = {
            count: 1,
            resetTime: now + RATE_LIMIT_WINDOW
        };
        return true;
    }
}

/**
 * Ověří a vrátí index dne v týdnu z požadavku, za předpokladu, že byl předán, a je zároveň
 * roven nebo vyšší indexu dnešního dne.
 * 
 * @param req request
 * @returns index dne v týdnu
 */
const parseValidateFutureDayIndex = (req: Request<{}, any, AddChoiceData["body"] | UpdateNoteData["body"]>) => {
    if (req.body.dayIndex == null) {
        throw Error(`Nebyl předán index dne v týdnu.`);
    }
    const todayDayIndex = getDayOfWeekIndex(getToday());
    const dayIndex = req.body.dayIndex;
    if (isNaN(dayIndex)) {
        throw Error(`Neplatný index dne v týdnu: ${req.body.dayIndex}`);
    }
    if (dayIndex < todayDayIndex) {
        throw Error(`Předaný index dne v týdnu (${dayIndex}) nesmí být nižší než dnešní den (${todayDayIndex})`);
    }
    return dayIndex;
}

const parseSlot = (body: Record<string, any>): MealSlot | undefined => {
    const slot = body?.slot;
    if (slot != null && slot !== MealSlot.OBED) {
        throw Error(`Neplatný slot: ${slot}`);
    }
    return slot ?? undefined;
};

const router = express.Router();

router.post("/addChoice", async (req: Request<{}, any, AddChoiceData["body"]>, res, next) => {
    const login = getLogin(parseToken(req));
    const trusted = getTrusted(parseToken(req));
    let slot: MealSlot | undefined;
    try { slot = parseSlot(req.body); } catch (e: any) { return res.status(400).json({ error: e.message }); }
    let date = undefined;
    if (req.body.dayIndex != null) {
        let dayIndex;
        try {
            dayIndex = parseValidateFutureDayIndex(req);
        } catch (e: any) {
            return res.status(400).json({ error: e.message });
        }
        date = getDateForWeekIndex(dayIndex);
    }
    try {
        const data = await addChoice(login, trusted, req.body.locationKey, req.body.foodIndex, date, slot);
        getWebsocket().emit("message", data);
        return res.status(200).json(data);
    } catch (e: any) { next(e) }
});

router.post("/removeChoices", async (req: Request<{}, any, RemoveChoicesData["body"]>, res, next) => {
    const login = getLogin(parseToken(req));
    const trusted = getTrusted(parseToken(req));
    let slot: MealSlot | undefined;
    try { slot = parseSlot(req.body); } catch (e: any) { return res.status(400).json({ error: e.message }); }
    let date = undefined;
    if (req.body.dayIndex != null) {
        let dayIndex;
        try {
            dayIndex = parseValidateFutureDayIndex(req);
        } catch (e: any) {
            return res.status(400).json({ error: e.message });
        }
        date = getDateForWeekIndex(dayIndex);
    }
    try {
        const data = await removeChoices(login, trusted, req.body.locationKey, date, slot);
        getWebsocket().emit("message", data);
        res.status(200).json(data);
    } catch (e: any) { next(e) }
});

router.post("/removeChoice", async (req: Request<{}, any, RemoveChoiceData["body"]>, res, next) => {
    const login = getLogin(parseToken(req));
    const trusted = getTrusted(parseToken(req));
    let slot: MealSlot | undefined;
    try { slot = parseSlot(req.body); } catch (e: any) { return res.status(400).json({ error: e.message }); }
    let date = undefined;
    if (req.body.dayIndex != null) {
        let dayIndex;
        try {
            dayIndex = parseValidateFutureDayIndex(req);
        } catch (e: any) {
            return res.status(400).json({ error: e.message });
        }
        date = getDateForWeekIndex(dayIndex);
    }
    try {
        const data = await removeChoice(login, trusted, req.body.locationKey, req.body.foodIndex, date, slot);
        getWebsocket().emit("message", data);
        res.status(200).json(data);
    } catch (e: any) { next(e) }
});

router.post("/updateNote", async (req: Request<{}, any, UpdateNoteData["body"]>, res, next) => {
    const login = getLogin(parseToken(req));
    const trusted = getTrusted(parseToken(req));
    const note = req.body.note;
    let slot: MealSlot | undefined;
    try { slot = parseSlot(req.body); } catch (e: any) { return res.status(400).json({ error: e.message }); }
    try {
        if (note && note.length > 70) {
            throw Error("Poznámka může mít maximálně 70 znaků");
        }
        let date = undefined;
        if (req.body.dayIndex != null) {
            let dayIndex;
            try {
                dayIndex = parseValidateFutureDayIndex(req);
            } catch (e: any) {
                return res.status(400).json({ error: e.message });
            }
            date = getDateForWeekIndex(dayIndex);
        }
        const data = await updateNote(login, trusted, note, date, slot);
        getWebsocket().emit("message", data);
        res.status(200).json(data);
    } catch (e: any) { next(e) }
});

router.post("/changeDepartureTime", async (req: Request<{}, any, ChangeDepartureTimeData["body"]>, res, next) => {
    const login = getLogin(parseToken(req));
    let date = undefined;
    if (req.body.dayIndex != null) {
        let dayIndex;
        try {
            dayIndex = parseValidateFutureDayIndex(req);
        } catch (e: any) {
            return res.status(400).json({ error: e.message });
        }
        date = getDateForWeekIndex(dayIndex);
    }
    try {
        const data = await updateDepartureTime(login, req.body?.time, date);
        getWebsocket().emit("message", data);
        res.status(200).json(data);
    } catch (e: any) { next(e) }
});

router.post("/jdemeObed", async (req, res, next) => {
    const login = getLogin(parseToken(req));
    try {
        await callNotifikace({ input: { user: login, udalost: UdalostEnum.JDEME_NA_OBED }, gotify: false })
        res.status(200).json({});
    } catch (e: any) { next(e) }
});

router.post("/updateBuyer", async (req, res, next) => {
    const login = getLogin(parseToken(req));
    let slot: MealSlot | undefined;
    try { slot = parseSlot(req.body ?? {}); } catch (e: any) { return res.status(400).json({ error: e.message }); }
    try {
        const data = await updateBuyer(login, slot);
        getWebsocket().emit("message", data);
        res.status(200).json({});
    } catch (e: any) { next(e) }
});

// /api/food/refresh?type=week  (přihlášený uživatel, nebo ?heslo=... pro bypass rate limitu)
export const refreshMetoda = async (req: Request, res: Response) => {
    const { type, heslo } = req.query as { type?: string; heslo?: string };
    const bypassPassword = process.env.REFRESH_BYPASS_PASSWORD;
    const isBypass = !!bypassPassword && heslo === bypassPassword;

    if (!isBypass) {
        try {
            getLogin(parseToken(req));
        } catch {
            return res.status(403).json({ error: "Přihlaste se prosím" });
        }
    }
    if (!checkRateLimit("refresh") && !isBypass) {
        return res.status(429).json({ error: "Refresh už se zavolal, chvíli počkej :))" });
    }
    if (type !== "week" && type !== "day") {
        return res.status(400).json({ error: "Neznámý typ refresh" });
    }
    if (type === "day") {
        return res.status(400).json({ error: "ještě neumim TODO..." });
    }
    try {
        // Pro všechny restaurace refreshni menu na aktuální týden
        const restaurants = ["SLADOVNICKA", "TECHTOWER", "ZASTAVKAUMICHALA", "SENKSERIKOVA"] as const;
        const firstDay = getFirstWorkDayOfWeek(getToday());
        const results: Record<string, any> = {};
        const successfulRestaurants: string[] = [];
        const failedRestaurants: string[] = [];

        // Nejdříve načíst všechna data bez ukládání
        for (const rest of restaurants) {
            try {
                const weekData = await fetchRestaurantWeekMenuData(rest, firstDay);
                results[rest] = weekData;

                // Kontrola validity dat
                if (weekData && weekData.length > 0 &&
                    weekData.some(dayMenu => dayMenu && dayMenu.length > 0)) {
                    successfulRestaurants.push(rest);
                } else {
                    failedRestaurants.push(rest);
                    results[rest] = { error: "Žádná validní data" };
                }
            } catch (error) {
                failedRestaurants.push(rest);
                results[rest] = { error: `Chyba při načítání: ${error}` };
            }
        }

        // Pokud se nepodařilo načíst žádnou restauraci
        if (successfulRestaurants.length === 0) {
            return res.status(400).json({
                error: "Nepodařilo se získat validní data z žádné restaurace",
                failed: failedRestaurants,
                results: results
            });
        }

        // Uložit pouze validní data
        for (const rest of successfulRestaurants) {
            try {
                await saveRestaurantWeekMenu(rest as any, firstDay, results[rest]);
            } catch (error) {
                console.error(`Chyba při ukládání dat pro ${rest}:`, error);
            }
        }

        // Připravit odpověď
        const response: any = {
            ok: true,
            refreshed: results,
            successful: successfulRestaurants
        };

        if (failedRestaurants.length > 0) {
            response.warning = `Nepodařilo se načíst: ${failedRestaurants.join(', ')}`;
            response.failed = failedRestaurants;
        }

        res.status(200).json(response);
    } catch (e: any) {
        res.status(500).json({ error: e?.message || "Chyba při refreshi" });
    }
}
router.get("/refresh", refreshMetoda);


export default router;