import HttpRouter from "@src/lib/router.ts";
import { Eta } from "@eta-dev/eta";
import { serveFile } from "jsr:@std/http/file-server";
import rateLimitMiddleware from "@src/middleware/rateLimiter.ts";
import authMiddleware from "@src/middleware/auth.ts";
import loggerMiddleware from "@src/middleware/logger.ts";
import {
    loginApi,
    passwordSetupApi,
    updateDevicesApi,
    versionApi,
} from "./api.ts";
import { err, ok } from "@shared/utils/result.ts";
import admin from "@src/lib/admin.ts";
import { Context } from "@src/lib/context.ts";
import {
    FailedToParseRequestAsJSONError,
    invalidPasswordError,
    passwordsMustMatchError,
    QueryExecutionError,
    queryExecutionError,
    RequestValidationError,
} from "@src/lib/errors.ts";
import devices from "@src/lib/devices.ts";

const AUTH_COOKIE_NAME = "token";
const VERSION = "0.1.0-a.1";

const router = new HttpRouter();

const views = Deno.cwd() + "/views/";
export const eta = new Eta({ views });

router.use(loggerMiddleware);
router.use(rateLimitMiddleware);
router.use(authMiddleware);

const cache: Map<string, Response> = new Map();

router.get("/public/*", async (c) => {
    const filePath = "." + c.path;

    //const cached = cache.get(filePath);
    //
    //if (cached) {
    //    return cached.clone();
    //}

    const res = await serveFile(c.req, filePath);

    //cache.set(filePath, res.clone());

    return res;
});

router
    .get(["", "/index.html"], (c) => {
        const devicesList = devices.list().unwrap().unwrap();

        return c.html(eta.render("./index.html", { devices: devicesList }));
    })
    .get(["/login", "/login.html"], (c) => {
        const isSet = admin.isPasswordSet();

        if (isSet.isErr()) {
            return c.html(eta.render("./internal_error.html", {}));
        }

        if (!isSet.value) {
            return c.redirect("/setup");
        }

        const alreadyLoggedIn = c.cookies.get("token").map((token) =>
            admin.sessions.verifyToken(token)
        )
            .toBoolean();

        console.log(alreadyLoggedIn);

        return c.html(eta.render("./login.html", { alreadyLoggedIn }));
    })
    .get("/setup", (c) => {
        return admin.isPasswordSet()
            .match(
                (isSet) => {
                    if (isSet) {
                        return c.redirect("/login");
                    } else {
                        return c.html(eta.render("./setup.html", {}));
                    }
                },
                (e) => c.html(eta.render("./internal_error.html", {})),
            );
    });

router.get("/api/admin/ws", (c) => {
    if (c.req.headers.get("upgrade") != "websocket") {
        return new Response(null, { status: 501 });
    }

    const { socket, response } = Deno.upgradeWebSocket(c.req);

    socket.addEventListener("open", () => {
        console.log("a client connected!");
    });

    socket.addEventListener("close", () => {
        console.log("client disconnected");
    });

    socket.addEventListener("message", (event) => {
        if (event.data === "ping") {
            console.log("ping");
            socket.send("pong");
        }
    });

    return response;
});

router
    .api(loginApi, async (c) => {
        const r = await c
            .parseBody()
            .andThenAsync(
                ({ password }) => admin.verifyPassword(password),
            );

        if (r.isErr()) {
            if (r.error.type === "AdminPasswordNotSetError") {
                return c.json400(
                    err({
                        type: r.error.type,
                        info: r.error.info,
                    }),
                );
            }
            return handleCommonErrors(c, r.error);
        }

        const isMatch = r.value;

        if (isMatch) {
            return admin.sessions.create()
                .map(({ value, expires }) => {
                    c.cookies.set({
                        name: AUTH_COOKIE_NAME,
                        value,
                        expires,
                    });
                    return ok();
                }).match(
                    (v) => c.json(v),
                    (e) => handleCommonErrors(c, e),
                );
        } else {
            return c.json(
                err(invalidPasswordError("Invalid login or password")),
            );
        }
    })
    .api(passwordSetupApi, async (c) => {
        const r = await c.parseBody();

        if (r.isErr()) {
            return handleCommonErrors(c, r.error);
        }

        const v = r.value;

        if (v.password !== v.passwordRepeat) {
            return c.json400(
                err(passwordsMustMatchError("Passwords must match")),
            );
        }

        return admin.setPassword(v.password).match(
            () => c.json(ok()),
            (e) => c.json400(err(e)),
        );
    })
    .api(updateDevicesApi, (c) => {
        return devices.updateDevices().match(
            () => c.json(ok()),
            (e) => c.json500(err(e)),
        );
    })
    .api(versionApi, (c) => {
        return c.json(ok({
            app: "Keyborg",
            version: VERSION,
        }));
    });

function handleCommonErrors(
    c: Context,
    error:
        | QueryExecutionError
        | FailedToParseRequestAsJSONError
        | RequestValidationError,
): Response {
    switch (error.type) {
        case "QueryExecutionError":
            return c.json(
                err(queryExecutionError("Server failed to execute query")),
                { status: 500 },
            );
        case "FailedToParseRequestAsJSONError":
            return c.json(
                err(error),
                { status: 400 },
            );
        case "RequestValidationError":
            return c.json(
                err(error),
                { status: 400 },
            );
    }
}

export default {
    async fetch(req, connInfo) {
        return await router.handleRequest(req, connInfo);
    },
} satisfies Deno.ServeDefaultExport;
