/**
 * VLESS URL Parser
 * Parses vless:// URLs and generates sing-box configurations.
 *
 * Philosophy: the app is a dumb pipe and fails CLOSED.
 * - ALL user traffic goes through the VPN, no exceptions (route.final = proxy).
 *   There is NO direct fallback for user traffic: if the server is down, traffic
 *   is blackholed inside the tunnel, never leaked. That IS the kill switch.
 * - All connection params come from the VLESS URL (exported by 3x-ui).
 * - FakeIP: no real DNS resolution of user traffic on the client side. A/AAAA
 *   queries get a fake IP (198.18/15); the real domain is recovered by sniffing
 *   and handed to the server, which resolves it with its own DNS (server is the
 *   source of truth).
 * - The "local" 8.8.8.8 server (detour: direct) exists ONLY to resolve the proxy
 *   server's OWN hostname before the tunnel is up. It MUST be an explicit external
 *   resolver, not the system resolver — under auto_route the system DNS is itself
 *   captured by the TUN and would resolve the server hostname to a fake IP.
 * - strict_route: true — Windows firewall rules so nothing slips around the TUN.
 *
 * NOTE: this DNS/route layout is deliberately tuned and proven. Do not "simplify"
 * or reorder it without testing a real tunnel end-to-end.
 */

function parseVlessUrl(vlessUrl) {
    try {
        const url = new URL(vlessUrl);

        if (url.protocol !== 'vless:') {
            throw new Error('Invalid protocol, expected vless://');
        }

        const params = new URLSearchParams(url.search);

        return {
            uuid: url.username,
            server: url.hostname,
            port: parseInt(url.port) || 443,
            name: decodeURIComponent(url.hash.slice(1)) || 'Unknown',
            type: params.get('type') || 'tcp',
            security: params.get('security') || 'none',
            flow: params.get('flow') || '',
            sni: params.get('sni') || '',
            fingerprint: params.get('fp') || '',
            alpn: params.get('alpn') || '',
            publicKey: params.get('pbk') || '',
            shortId: params.get('sid') || '',
            spiderX: params.get('spx') || '',
            path: params.get('path') || '',
            host: params.get('host') || '',
            serviceName: params.get('serviceName') || '',
        };
    } catch (error) {
        console.error('Failed to parse VLESS URL:', error);
        return null;
    }
}

function generateSingboxConfig(vlessConfig, options = {}) {
    const tunName = options.tunName || 'shadowspot-tun';
    const clashPort = options.clashPort || 9090;

    // === VLESS outbound — only params from the URL ===
    const outbound = {
        type: 'vless',
        tag: 'proxy',
        server: vlessConfig.server,
        server_port: vlessConfig.port,
        uuid: vlessConfig.uuid,
    };

    if (vlessConfig.flow) {
        outbound.flow = vlessConfig.flow;
    }

    if (vlessConfig.security === 'reality' || vlessConfig.security === 'tls') {
        const tls = { enabled: true };

        if (vlessConfig.sni) {
            tls.server_name = vlessConfig.sni;
        }

        if (vlessConfig.alpn) {
            tls.alpn = vlessConfig.alpn.split(',').map(a => a.trim()).filter(Boolean);
        }

        if (vlessConfig.fingerprint) {
            tls.utls = {
                enabled: true,
                fingerprint: vlessConfig.fingerprint,
            };
        }

        if (vlessConfig.security === 'reality') {
            tls.reality = {
                enabled: true,
                public_key: vlessConfig.publicKey,
                short_id: vlessConfig.shortId,
            };
        }

        outbound.tls = tls;
    }

    if (vlessConfig.type === 'ws') {
        outbound.transport = {
            type: 'ws',
            path: vlessConfig.path || '/',
        };
        if (vlessConfig.host) {
            outbound.transport.headers = { Host: vlessConfig.host };
        }
    } else if (vlessConfig.type === 'grpc') {
        outbound.transport = {
            type: 'grpc',
            service_name: vlessConfig.serviceName || '',
        };
    }

    return {
        log: {
            level: 'warning',
            timestamp: true,
        },
        dns: {
            servers: [
                {
                    // Default / bootstrap: resolves the proxy server's hostname.
                    // Must be an explicit external resolver (NOT the system one).
                    tag: 'local',
                    address: '8.8.8.8',
                    detour: 'direct',
                },
                {
                    // FakeIP: returns fake IPs from 198.18.0.0/15 pool.
                    // No real DNS query — domain passed to server via VLESS.
                    tag: 'fakeip',
                    address: 'fakeip',
                },
            ],
            rules: [
                {
                    // sing-box internal: resolve outbound server address via local DNS
                    outbound: 'any',
                    server: 'local',
                },
                {
                    // All A/AAAA queries → FakeIP (no real resolution)
                    query_type: ['A', 'AAAA'],
                    server: 'fakeip',
                },
            ],
            fakeip: {
                enabled: true,
                inet4_range: '198.18.0.0/15',
                inet6_range: 'fc00::/18',
            },
            strategy: 'prefer_ipv4',
            independent_cache: true,
        },
        inbounds: [
            {
                type: 'tun',
                tag: 'tun-in',
                interface_name: tunName,
                address: ['172.19.0.1/30'],
                mtu: 9000,
                auto_route: true,
                strict_route: true,
                stack: 'mixed',
            },
        ],
        outbounds: [
            outbound,
            {
                type: 'direct',
                tag: 'direct',
            },
        ],
        route: {
            auto_detect_interface: true,
            // No direct fallback for user traffic: server down => blackholed,
            // not leaked. This is the kill switch.
            final: 'proxy',
            rules: [
                {
                    // Sniff protocol on all incoming connections (recovers the
                    // real domain behind a FakeIP so the server can resolve it).
                    action: 'sniff',
                },
                {
                    // Intercept DNS packets from TUN → sing-box DNS module.
                    protocol: 'dns',
                    action: 'hijack-dns',
                },
            ],
        },
        experimental: {
            clash_api: {
                external_controller: `127.0.0.1:${clashPort}`,
                secret: '',
            },
        },
    };
}

module.exports = { parseVlessUrl, generateSingboxConfig };
