import express from "express";
import bodyParser from "body-parser";
import cors from 'cors';
import { getData, addChoice, getDateForWeekIndex, getToday } from "./service";
import { MealSlot } from "../../types/gen/types.gen";
import dotenv from 'dotenv';
import path from 'path';
import { getQr } from "./qr";
import { generateToken, getLogin, verify } from "./auth";
import { getIsWeekend, InsufficientPermissions, PizzaDayConflictError, parseToken } from "./utils";
import { getPendingQrs } from "./pizza";
import { initWebsocket, getWebsocket } from "./websocket";
import { startReminderScheduler, verifyQuickChoiceToken } from "./pushReminder";
import { storageReady } from "./storage";
import pizzaDayRoutes from "./routes/pizzaDayRoutes";
import foodRoutes, { refreshMetoda } from "./routes/foodRoutes";
import votingRoutes from "./routes/votingRoutes";
import easterEggRoutes from "./routes/easterEggRoutes";
import statsRoutes from "./routes/statsRoutes";
import notificationRoutes from "./routes/notificationRoutes";
import qrRoutes from "./routes/qrRoutes";
import devRoutes from "./routes/devRoutes";
import changelogRoutes from "./routes/changelogRoutes";
import groupRoutes from "./routes/groupRoutes";
import storeRoutes from "./routes/storeRoutes";

const ENVIRONMENT = process.env.NODE_ENV ?? 'production';
dotenv.config({ path: path.resolve(__dirname, `../.env.${ENVIRONMENT}`) });

// Validace nastavení JWT tokenu - nemá bez něj smysl vůbec povolit server spustit
if (!process.env.JWT_SECRET) {
    throw Error("Není vyplněna proměnná prostředí JWT_SECRET");
}

const app = express();
const server = require("http").createServer(app);
initWebsocket(server);

// Body-parser middleware for parsing JSON
app.use(bodyParser.json());

app.use(cors({
    origin: '*'
}));

// Zapínatelný login přes hlavičky - pokud je zapnutý nepovolí "basicauth"
const HTTP_REMOTE_USER_ENABLED = process.env.HTTP_REMOTE_USER_ENABLED === 'true' || false;
const HTTP_REMOTE_USER_HEADER_NAME = process.env.HTTP_REMOTE_USER_HEADER_NAME ?? 'remote-user';
if (HTTP_REMOTE_USER_ENABLED) {
    if (!process.env.HTTP_REMOTE_TRUSTED_IPS) {
        throw new Error('Je zapnutý login z hlaviček, ale není nastaven rozsah adres ze kterých hlavička může přijít.');
    }
    const HTTP_REMOTE_TRUSTED_IPS = process.env.HTTP_REMOTE_TRUSTED_IPS.split(',').map(ip => ip.trim());
    //TODO: nevim jak udelat console.log pouze pro "debug"
    //console.log("Budu věřit hlavičkám z: " + HTTP_REMOTE_TRUSTED_IPS);
    app.set('trust proxy', HTTP_REMOTE_TRUSTED_IPS);
    console.log('Zapnutý login přes hlavičky z proxy.');
}


// ----------- Metody nevyžadující token --------------

app.get("/api/health", (_req, res) => {
    res.status(200).json({ ok: true });
});

app.get("/api/whoami", (req, res) => {
    if (!HTTP_REMOTE_USER_ENABLED) {
        res.status(403).json({ error: 'Není zapnuté přihlášení z hlaviček' });
    }
    if (process.env.ENABLE_HEADERS_LOGGING === 'yes') {
        delete req.headers["cookie"]
        console.log(req.headers)
    }
    res.send(req.header(HTTP_REMOTE_USER_HEADER_NAME));
})

app.post("/api/login", (req, res) => {
    if (HTTP_REMOTE_USER_ENABLED) { // je rovno app.enabled('trust proxy')
        // Autentizace pomocí trusted headers
        const remoteUser = req.header(HTTP_REMOTE_USER_HEADER_NAME);
        //const remoteName = req.header('remote-name');
        if (remoteUser && remoteUser.length > 0) {
            res.status(200).json(generateToken(Buffer.from(remoteUser, 'latin1').toString(), true));
        } else {
            throw Error("Je zapnuto přihlášení přes hlavičky, ale nepřišla hlavička nebo ??");
        }
    } else {
        // Klasická autentizace loginem
        if (!req.body?.login || req.body.login.trim().length === 0) {
            throw Error("Nebyl předán login");
        }
        // TODO zavést podmínky pro délku loginu (min i max)
        res.status(200).json(generateToken(req.body.login, false));
    }
});

// QR se zobrazuje přes <img>, nemáme sem jak dostat token
app.get("/api/qr", async (req, res) => {
    if (!req.query?.login) {
        return res.status(400).json({ error: "Nebyl předán login" });
    }
    if (!req.query?.id) {
        return res.status(400).json({ error: "Nebyl předán identifikátor QR kódu" });
    }
    const img = await getQr(req.query.login as string, req.query.id as string);
    res.writeHead(200, {
        'Content-Type': 'image/png',
        'Content-Length': img.length
    });
    res.end(img);
});

// ----------------------------------------------------

// Přeskočení auth pro refresh dat xd
app.use("/api/food/refresh", refreshMetoda);

// Rychlá akce z push notifikace — autentizace pomocí HMAC tokenu z push payloadu (SW nemá přístup k JWT)
app.post("/api/notifications/push/quickChoice", async (req, res, next) => {
    try {
        const { login, token } = req.body ?? {};
        if (!login || typeof login !== 'string' || !token || typeof token !== 'string') {
            return res.status(400).json({ error: 'Chybí login nebo token' });
        }
        if (!verifyQuickChoiceToken(login, token)) {
            return res.status(403).json({ error: 'Neplatný token' });
        }
        const updatedData = await addChoice(login, false, 'NEOBEDVAM', undefined, undefined);
        getWebsocket().emit("message", updatedData);
        res.status(200).json({});
    } catch (e: any) { next(e); }
});

/** Middleware ověřující JWT token */
app.use("/api/", (req, res, next) => {
    if (HTTP_REMOTE_USER_ENABLED) {
        // Autentizace pomocí trusted headers
        const remoteUser = req.header(HTTP_REMOTE_USER_HEADER_NAME);
        if (process.env.ENABLE_HEADERS_LOGGING === 'yes') {
            delete req.headers["cookie"]
            console.log(req.headers)
        }
        if (remoteUser && remoteUser.length > 0) {
            const remoteName = Buffer.from(remoteUser, 'latin1').toString();
            if (ENVIRONMENT !== "production") {
                console.log("Tvuj username: %s.", remoteName);
            }
        }
    }
    if (!req.headers.authorization) {
        return res.status(401).json({ error: 'Nebyl předán autentizační token' });
    }
    const token = req.headers.authorization.split(' ')[1];
    if (!verify(token)) {
        return res.status(403).json({ error: 'Neplatný autentizační token' });
    }
    next();
});

/** Vrátí data pro aktuální den. */
app.get("/api/data", async (req, res) => {
    let date = undefined;
    if (req.query.dayIndex != null && typeof req.query.dayIndex === 'string') {
        const index = parseInt(req.query.dayIndex);
        if (!isNaN(index)) {
            date = getDateForWeekIndex(parseInt(req.query.dayIndex));
        }
    } else if (getIsWeekend(getToday())) {
        // Na víkendu zobrazíme pátek místo hlášky "Užívejte víkend"
        date = getDateForWeekIndex(4);
    }
    const slotParam = typeof req.query.slot === 'string' ? req.query.slot as MealSlot : undefined;
    if (slotParam && slotParam !== MealSlot.OBED && slotParam !== MealSlot.EXTRA) {
        return res.status(400).json({ error: 'Neplatný slot' });
    }
    const data = await getData(date, slotParam);
    // Připojíme nevyřízené QR kódy pro přihlášeného uživatele
    try {
        const login = getLogin(parseToken(req));
        const pendingQrs = await getPendingQrs(login);
        if (pendingQrs.length > 0) {
            data.pendingQrs = pendingQrs;
        }
    } catch {
        // Token nemusí být validní, ignorujeme
    }
    res.status(200).json(data);
});

// Ostatní routes
app.use("/api/pizzaDay", pizzaDayRoutes);
app.use("/api/food", foodRoutes);
app.use("/api/voting", votingRoutes);
app.use("/api/easterEggs", easterEggRoutes);
app.use("/api/stats", statsRoutes);
app.use("/api/notifications", notificationRoutes);
app.use("/api/qr", qrRoutes);
app.use("/api/dev", devRoutes);
app.use("/api/changelogs", changelogRoutes);
app.use("/api/groups", groupRoutes);
app.use("/api/stores", storeRoutes);

app.use('/stats', express.static('public'));
app.use(express.static('public'));

// Middleware pro zpracování chyb
app.use((err: any, req: any, res: any, next: any) => {
    if (err instanceof InsufficientPermissions) {
        res.status(403).send({ error: err.message })
    } else if (err instanceof PizzaDayConflictError) {
        res.status(409).send({ error: err.message })
    } else {
        res.status(500).send({ error: err.message })
    }
    next();
});

const PORT = process.env.PORT ?? 3001;
const HOST = process.env.HOST ?? '0.0.0.0';

storageReady.then(() => {
    server.listen(PORT, () => {
        console.log(`Server listening on ${HOST}, port ${PORT}`);
        startReminderScheduler();
    });
});

// Umožníme vypnutí serveru přes SIGINT, jinak Docker čeká než ho sestřelí
process.on('SIGINT', function () {
    console.log("\nSIGINT (Ctrl-C), vypínám server");
    process.exit(0);
});