import express from 'express';
import cors from 'cors';
import ipcidr from 'ip-cidr';

import { logSystem, LogSystem } from '../core/log';
import { RestRouteEntity } from '../entities/rest-route.entity';
import { getConnection, getRepository } from 'typeorm';
import { RpcKeyEntity } from '../entities/rpc-key.entity';
import RpcRouterGroup from './modules/group/group.rpc-module';
import RpcRouterCharacter from './modules/character/character.rpc-module';
import RpcRouterVehicle from './modules/vehicle/vehicle.rpc-module';
import * as bodyParser from 'body-parser';
import RpcRouterProperty from './modules/property/property.rpc-module';
import RpcRouterAccount from './modules/account/account.rpc-module';
import RpcRouterPlayers from './modules/players/players.rpc-module';
import RpcRouterMotd from './modules/motd/motd.rpc-module';
import RpcRouterFaction from './modules/faction/faction.rpc-module';
import RpcRouterChat from './modules/chat/chat.rpc-module';
import RpcRouterCompany from './modules/company/company.rpc-module';

export class RestServer {
    static PORT = 3001;

    app: express.Application;
    routeEntities: RestRouteEntity[] = [];

    private readonly allowedOrigins = this.resolveAllowedOrigins();

    constructor() {
        this.app = express();

        this.app.use(
            cors({
                origin: (origin, callback) => {
                    if (!origin) {
                        callback(null, true);
                        return;
                    }

                    const normalizedOrigin = this.normalizeOrigin(origin);
                    callback(null, this.allowedOrigins.includes(normalizedOrigin));
                },
            }),
        );
        this.app.use(bodyParser.json());
        this.app.use(this.permissionMiddleware.bind(this));
        this.app.use('/group', RpcRouterGroup);
        this.app.use('/character', RpcRouterCharacter);
        this.app.use('/vehicle', RpcRouterVehicle);
        this.app.use('/property', RpcRouterProperty);
        this.app.use('/account', RpcRouterAccount);
        this.app.use('/players', RpcRouterPlayers);
        this.app.use('/motd', RpcRouterMotd);
        this.app.use('/faction', RpcRouterFaction);
        this.app.use('/chat', RpcRouterChat);
        this.app.use('/company', RpcRouterCompany);
    }

    async permissionMiddleware(req: express.Request, res: express.Response, next: express.NextFunction): Promise<express.Response | void> {
        const rpcKey = await getConnection()
            .getRepository(RpcKeyEntity)
            .findOne({
                where: {
                    key: req.headers.rpckey,
                },
            });

        if (!rpcKey) {
            await LogSystem.Error('RestServer', 'permissionMiddleware', `Unauthorized access attempt: no RPC key matches: ${req.headers.rpckey}`, {
                method: req.method,
                path: req.originalUrl,
                ip: req.ip,
                headers: req.headers,
            });
            return res.sendStatus(401);
        }

        if (rpcKey.ip) {
            const cidr = new ipcidr(rpcKey.ip);
            if (!cidr.contains(req.ip)) {
                await LogSystem.Error('RestServer', 'permissionMiddleware', `Unauthorized access attempt: IP ${req.ip} not in allowed range ${rpcKey.ip}`, {
                    method: req.method,
                    path: req.originalUrl,
                    ip: req.ip,
                    headers: req.headers,
                });
                return res.sendStatus(401);
            }
        }

        next();
    }

    async init(): Promise<void> {
        this.app.listen(RestServer.PORT, '0.0.0.0', () => {
            logSystem.initLog('RestServer', `Listening on port ${RestServer.PORT}`);
        });

        this.routeEntities = await getRepository(RestRouteEntity).find();
    }

    private resolveAllowedOrigins(): string[] {
        const defaults = ['http://127.0.0.1', 'http://localhost'];
        const configured = (process.env.SWRP_UCP_URL ?? '')
            .split(',')
            .map((origin) => this.normalizeOrigin(origin))
            .filter((origin) => origin.length > 0);

        const origins = configured.length > 0 ? configured : defaults;
        return [...new Set(origins)];
    }

    private normalizeOrigin(origin: string): string {
        return origin.trim().replace(/\/+$/, '');
    }
}

export const restServer = new RestServer();
