import ejs from "ejs";
import Fastify from "fastify";

import { readdir, stat } from "fs/promises";
import { IncomingMessage, Server, ServerResponse } from "http";
import { join } from "path";

import { environment } from "../../config/environment";

// types
import type { FastifyInstance, FastifyRequest } from "fastify";
import type { Stats } from "fs";
import type { AddressInfo } from "net";

// official plugins
import { fastifyStatic } from "@fastify/static";
import { fastifyView } from "@fastify/view";
import fastifyWebSocket from '@fastify/websocket';

//custom plugins
import faviconPlugin from "./plugins/favicon";

class FastifyManager {
    private readonly port: number = environment.fastify.port;
    private readonly server: FastifyInstance<Server, IncomingMessage, ServerResponse>;

    constructor() {
        this.server = Fastify({
            logger: false,
            trustProxy: !environment.development,
            ignoreTrailingSlash: true
        });
    }
    private async registerPlugins(): Promise<void> {
        // official plugins
        this.server.register(fastifyView, {
            engine: {
                ejs
            },
            root: environment.paths.www.views,
            includeViewExtension: true
        });

        this.server.register(fastifyStatic, {
            root: environment.paths.www.public,
            prefix: "/public/",
            prefixAvoidTrailingSlash: true
        });

        await this.server.register(fastifyWebSocket, {
            options: {
                maxPayload: 1048576
            }
        });

        // custom plugins
        this.server.register(faviconPlugin, { path: join(environment.paths.www.public, "assets", "favicon.gif") });
    }

    // dynamic route loading
    private async loadRoutes(): Promise<void> {
        const routePaths: [string, string, boolean][] = [
            ["routes", "/", false],
            ["../api", "/api", true]
        ];

        for (const [routePath, prefix, recursive] of routePaths) {
            const modifiedRoutePath: string = join(environment.paths.www.root, routePath);

            let files: string[];
            try {
                files = await this.readDirRecursive(modifiedRoutePath, recursive);
            } catch (err) {
                console.error("Failed to read route directory", modifiedRoutePath, "error:", err);
                return;
            }

            for (const file of files) {
                try {
                    const routeModule = await import(file);
                    const { default: routeData } = routeModule;

                    if (!routeData || !routeData.routeInfo || !routeData.route) {
                        console.error(`Failed to load route from ${file}:`, "Route data is missing");
                        continue;
                    }

                    if (routeData.routeInfo.enabled && routeData.route) {
                        const { routeInfo, route } = routeData;

                        let routePath = routeInfo.path || "/";

                        if (prefix) {
                            routePath = routePath === "/" ? prefix : join(prefix, routePath);
                        }

                        routePath = routePath.replace(/\\/g, '/');

                        if (!routePath.startsWith('/')) {
                            routePath = '/' + routePath;
                        }

                        routePath = routePath.replace(/\/+/g, "/");
                        const methods = Array.isArray(routeInfo.method) ? routeInfo.method : [routeInfo.method];

                        for (const method of methods) {
                            if (routeInfo.websocket) {
                                this.server.get(routePath, { websocket: true }, function wsHandler(socket: any, req: FastifyRequest) {
                                    route(socket, req);
                                });
                                continue;
                            } else {
                                this.server.route({
                                    method,
                                    url: routePath,
                                    handler: route
                                });
                            }
                        }
                    }
                } catch (err) {
                    console.error(`Failed to load route from ${file}:`, err);
                }
            }
        }
    }

    private async readDirRecursive(dir: string, recursive: boolean): Promise<string[]> {
        let results: string[] = [];
        const list: string[] = await readdir(dir);

        for (const file of list) {
            const filePath: string = join(dir, file);
            const statObj: Stats = await stat(filePath);
            if (statObj && statObj.isDirectory()) {
                if (recursive) {
                    results = results.concat(await this.readDirRecursive(filePath, recursive));
                }
            } else {
                results.push(filePath);
            }
        }

        return results;
    }

    public async start(): Promise<void> {
        try {
            await this.registerPlugins();
            await this.loadRoutes();

            await this.server.listen({
                port: environment.fastify.port,
                host: environment.fastify.host
            });

            const [_address, _port, scheme]: [string, number, string] = ((): [string, number, string] => {
                const address: string | AddressInfo | null = this.server.server.address();
                const resolvedAddress: [string, number] = (address && typeof address === 'object') ? [(address.address.startsWith("::") || address.address === "0.0.0.0") ? "localhost" : address.address, address.port] : ["localhost", this.port];
                const resolvedScheme: string = resolvedAddress[0] === "localhost" ? "http" : "https";

                return [...resolvedAddress, resolvedScheme];
            })();


            console.info("Started Listening on", `${scheme}://${_address}:${_port}`);
            console.info("Registered routes: ", "\n", this.server.printRoutes());
        } catch (error) {
            console.error("Failed to start Fastify server", error);
        }
    }
}

const fastifyManager = new FastifyManager();
export default fastifyManager;
export { fastifyManager };
