const fs = require('fs');
const http = require('http');
const path = require('path');
const { URL } = require('url');

const ROOT_DIR = __dirname;
const PARENT_DIR = path.join(__dirname, '..', '..');
const INDEX_PATH = path.join(ROOT_DIR, 'index.html');
const ENV_PATH = path.join(ROOT_DIR, '.env');
const HOST = '127.0.0.1';
const PORT = Number(process.env.PORT || 3000);

function logServerError(scope, error, extra = {}) {
    console.error(`[${scope}]`, error && error.stack ? error.stack : error, extra);
}

function parseEnvFile(filePath) {
    if (!fs.existsSync(filePath)) {
        return {};
    }

    const env = {};
    const lines = fs.readFileSync(filePath, 'utf8').split(/\r?\n/);

    for (const rawLine of lines) {
        const line = rawLine.trim();
        if (!line || line.startsWith('#')) {
            continue;
        }

        const separatorIndex = line.indexOf('=');
        if (separatorIndex === -1) {
            continue;
        }

        const key = line.slice(0, separatorIndex).trim();
        let value = line.slice(separatorIndex + 1).trim();

        if (
            (value.startsWith('"') && value.endsWith('"')) ||
            (value.startsWith("'") && value.endsWith("'"))
        ) {
            value = value.slice(1, -1);
        }

        env[key] = value;
    }

    return env;
}

function getAppConfig() {
    const env = {
        ...parseEnvFile(ENV_PATH),
        ...process.env
    };

    const dashboardProxyTemplates = [
        env.DASHBOARD_PROXY_1,
        env.DASHBOARD_PROXY_2,
        env.DASHBOARD_PROXY_3
    ].filter(Boolean);

    return {
        dashboardTargetUrl: env.DASHBOARD_TARGET_URL || '',
        dashboardProxyTemplates
    };
}

const DASHBOARD_CACHE_TTL_MS = 5 * 60 * 1000;
let dashboardCache = { html: null, timestamp: 0 };

async function fetchDashboardHtml() {
    const targetUrl = getAppConfig().dashboardTargetUrl;
    if (!targetUrl) {
        throw new Error('DASHBOARD_TARGET_URL kosong');
    }

    const now = Date.now();
    if (dashboardCache.html && (now - dashboardCache.timestamp) < DASHBOARD_CACHE_TTL_MS) {
        return { html: dashboardCache.html, cached: 'HIT' };
    }

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 12000);
    try {
        const response = await fetch(targetUrl, {
            signal: controller.signal,
            headers: {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36'
            }
        });
        if (!response.ok) {
            throw new Error(`HTTP_ERROR_${response.status}`);
        }
        const html = await response.text();
        dashboardCache = { html, timestamp: Date.now() };
        return { html, cached: 'MISS' };
    } catch (error) {
        if (dashboardCache.html) {
            return { html: dashboardCache.html, cached: 'STALE' };
        }
        throw error;
    } finally {
        clearTimeout(timeoutId);
    }
}

function validateAppConfig(config) {
    const errors = [];

    if (!config.dashboardTargetUrl) {
        errors.push('DASHBOARD_TARGET_URL belum diisi.');
    }

    if (!Array.isArray(config.dashboardProxyTemplates) || !config.dashboardProxyTemplates.length) {
        errors.push('Minimal satu DASHBOARD_PROXY_* harus diisi.');
    }

    return errors;
}

function injectConfig(html) {
    const configScript = `<script>window.APP_CONFIG = ${JSON.stringify(getAppConfig())};</script>`;
    const defaultConfigScript = '<script>\n        window.APP_CONFIG = window.APP_CONFIG || {\n            dashboardTargetUrl: \'\',\n            dashboardProxyTemplates: []\n        };\n    </script>';

    if (html.includes(defaultConfigScript)) {
        return html.replace(defaultConfigScript, configScript);
    }

    return html.replace('</head>', `    ${configScript}\n</head>`);
}

function sendHtml(res) {
    try {
        const html = fs.readFileSync(INDEX_PATH, 'utf8');
        const configErrors = validateAppConfig(getAppConfig());
        if (configErrors.length) {
            logServerError('config', new Error('Invalid app config'), { configErrors });
        }

        const withConfig = injectConfig(html);
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        res.end(withConfig);
    } catch (error) {
        logServerError('sendHtml', error);
        res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
        res.end('Internal Server Error');
    }
}

function getMimeType(pathname) {
    const ext = path.extname(pathname).toLowerCase();
    const mimeTypes = {
        '.html': 'text/html; charset=utf-8',
        '.css': 'text/css; charset=utf-8',
        '.js': 'application/javascript; charset=utf-8',
        '.json': 'application/json; charset=utf-8',
        '.png': 'image/png',
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.gif': 'image/gif',
        '.svg': 'image/svg+xml',
        '.ico': 'image/x-icon',
        '.webp': 'image/webp',
        '.woff': 'font/woff',
        '.woff2': 'font/woff2',
        '.ttf': 'font/ttf',
        '.eot': 'application/vnd.ms-fontobject'
    };
    return mimeTypes[ext] || 'application/octet-stream';
}

function sendStaticFile(res, filepath) {
    try {
        if (!fs.existsSync(filepath)) {
            res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
            res.end('Not found');
            return;
        }

        const content = fs.readFileSync(filepath);
        const mimeType = getMimeType(filepath);
        res.writeHead(200, { 'Content-Type': mimeType });
        res.end(content);
    } catch (error) {
        logServerError('sendStaticFile', error, { filepath });
        res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
        res.end('Internal Server Error');
    }
}

function sendNotFound(res) {
    res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.end('Not found');
}

const server = http.createServer((req, res) => {
    try {
        const requestUrl = new URL(req.url, `http://${req.headers.host}`);
        const pathname = requestUrl.pathname;

        if (pathname === '/' || pathname === '/index.html') {
            sendHtml(res);
            return;
        }

        if (pathname === '/api/dashboard') {
            fetchDashboardHtml()
                .then(({ html, cached }) => {
                    res.writeHead(200, {
                        'Content-Type': 'text/html; charset=utf-8',
                        'X-Cache': cached
                    });
                    res.end(html);
                })
                .catch((error) => {
                    logServerError('api-dashboard', error);
                    res.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' });
                    res.end('DASHBOARD_FETCH_FAILED');
                });
            return;
        }

        // Handle /assets/ from parent directory
        if (pathname.startsWith('/assets/')) {
            const assetsFilepath = path.join(PARENT_DIR, pathname);
            const normalizedAssetsPath = path.normalize(assetsFilepath);
            
            if (!normalizedAssetsPath.startsWith(path.normalize(PARENT_DIR))) {
                sendNotFound(res);
                return;
            }
            
            sendStaticFile(res, normalizedAssetsPath);
            return;
        }

        // Serve static files from ROOT_DIR
        const filepath = path.join(ROOT_DIR, pathname);
        const normalizedPath = path.normalize(filepath);
        
        // Security check: ensure the requested file is within ROOT_DIR
        if (!normalizedPath.startsWith(path.normalize(ROOT_DIR))) {
            sendNotFound(res);
            return;
        }

        sendStaticFile(res, normalizedPath);
    } catch (error) {
        logServerError('request-handler', error, { url: req.url });
        res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
        res.end('Internal Server Error');
    }
});

server.on('clientError', (error, socket) => {
    logServerError('clientError', error);
    socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});

server.listen(PORT, HOST, () => {
    console.log(`Server ready at http://${HOST}:${PORT}`);
});

server.on('error', (error) => {
    logServerError('server', error, { host: HOST, port: PORT });
    process.exitCode = 1;
});

process.on('uncaughtException', (error) => {
    logServerError('uncaughtException', error);
    process.exitCode = 1;
});

process.on('unhandledRejection', (reason) => {
    logServerError('unhandledRejection', reason instanceof Error ? reason : new Error(String(reason)));
    process.exitCode = 1;
});
