/*
 * Copyright (c) 2025 Genexis B.V. All rights reserved.
 *
 * This Software and its content are protected by the Dutch Copyright Act
 * ('Auteurswet'). All and any copying and distribution of the software
 * and its content without authorization by Genexis B.V. is
 * prohibited. The prohibition includes every form of reproduction and
 * distribution.
 *
 */

import { getUciByType } from '../uci.js';
import { getBridgePortType, getTPIDFromDeviceType } from './common.js';

// find the port from bridge-vlan; returns [vlanId, isTagged, isPvid] or null
function findPortFromBridgeVlan(bridgeVlans, portName) {
    if (!bridgeVlans) return null;

    for (const bridgeVlan of bridgeVlans) {
        const port = bridgeVlan.ports?.find(x => x.split(':')[0] === portName);
        if (port) {
            const flags = port.includes(':') ? port.split(':')[1] : '';
            return [bridgeVlan.vlan, flags.includes('t'), flags.includes('*')];
        }
    }
    return null;
}

function isVLANSubInterface(portName, ethPorts) {
    const names = portName.split('.');
    if (names.length > 1) {
        const baseIfname = names.slice(0, -1).join('.');
        if (ethPorts.find(x => x.ifname === baseIfname)) {
            return true;
        }
    }
    return false;
}

function createProviderBridge(dev, type, ethIndex, bridges, providerBridges, cVLANBridgeIndex) {
    const briIndex = bridges.length + 1;

    let sVLANBridgeIndex = bridges.findIndex((x) => x['VLAN.']?.[0]?.VLANID === dev.vid && x['Port.']?.[0]?.LowerLayers === `Device.Ethernet.Interface.${ethIndex}`);
    if (sVLANBridgeIndex < 0) {
        // no management port needed for provider bridge
        bridges.push({
            Name: dev.name,
            Alias: `cpe-${dev.name}`,
            Standard: '802.1Q-2011',
            Enable: 1,
            'Port.': [{
                Enable: 1,
                Name: dev.name,
                Alias: `cpe-${dev.name}`,
                TPID: 34984,
                PVID: 1,
                Type: 'ProviderNetworkPort',
                LowerLayers: `Device.Ethernet.Interface.${ethIndex}`,
            }],
            'VLAN.': [{
                Enable: 1,
                VLANID: dev.vid,
            }],
            'VLANPort.': [{
                Enable: 1,
                VLAN: `Device.Bridging.Bridge.${briIndex}.VLAN.1`,
                Port: `Device.Bridging.Bridge.${briIndex}.Port.1`,
                Untagged: 1,
            }],
            _key: dev['.name'],
        });
        sVLANBridgeIndex = bridges.length;
    } else {
        sVLANBridgeIndex = sVLANBridgeIndex + 1;
        const pvBridge = providerBridges.find(x => x.SVLANcomponent === `Device.Bridging.Bridge.${sVLANBridgeIndex}`);
        if (pvBridge) {
            pvBridge.CVLANcomponents = pvBridge.CVLANcomponents + `,Device.Bridging.Bridge.${cVLANBridgeIndex}`;
            return;
        }
    }

    providerBridges.push({
        Alias: `cpe-${dev.name}`,
        Enable: 1,
        Type: type,
        SVLANcomponent: `Device.Bridging.Bridge.${sVLANBridgeIndex}`,
        CVLANcomponents: `Device.Bridging.Bridge.${cVLANBridgeIndex}`,
        _key: dev['.name'],
    });
}

function addRegularEthernetPort(ethDevice, portIndex, briPorts) {
    const tpid = getTPIDFromDeviceType(ethDevice.type, ethDevice.tpid);

    briPorts.push({
        Enable: 1,
        Name: ethDevice['ifname'],
        Alias: `cpe-${ethDevice['.name']}`,
        TPID: tpid,
        PVID: 1,
        Type: 'CustomerVLANPort',
        LowerLayers: `Device.Ethernet.Interface.${portIndex + 1}`,
        _key: ethDevice['.name'],
    });
}


function handleVlanDevice(bridgeIndex, device, ethPorts, devices, bridges, briPorts, briVLAN, briVLANPort, providerBridges) {
    let qinqDev;
    let ethIndex = ethPorts.findIndex(x => device.ifname === x.ifname);

    if (device.type === '8021ad') {
        if (ethIndex < 0) {
            _log_error('base ethernet device not found', device.ifname);
            return;
        }
        createProviderBridge(device, 'S-VLAN', ethIndex + 1, bridges, providerBridges, bridgeIndex);
        return;
    }

    if (ethIndex < 0) {
        qinqDev = devices.find(x => x.name === device.ifname);
        if (!qinqDev || !qinqDev.ifname) {
            _log_error('device ifname not found', device.ifname);
            return;
        }
        if (qinqDev.type !== '8021ad' || device.type !== '8021q') {
            _log_error('invalid qinq device type', qinqDev['.name'], device['.name']);
            return;
        }
        device.ifname = qinqDev.ifname;
        ethIndex = ethPorts.findIndex(x => device.ifname === x.ifname);
        if (ethIndex < 0) {
            _log_error('base ethernet device not found', device.ifname);
            return;
        }
    }

    if (device.type !== '8021q') {
        _log_error('unsupported device type', device['.name'], device.type);
        return;
    }

    let vlanIndex = briVLAN.findIndex(x => Number(x.VLANID) === Number(device.vid));
    if (vlanIndex < 0) {
        briVLAN.push({ Enable: 1, VLANID: Number(device.vid) });
        vlanIndex = briVLAN.length;
    } else {
        vlanIndex += 1;
    }

    const portType = qinqDev ? 'CustomerEdgePort' : getBridgePortType(device.type);
    const tpid = getTPIDFromDeviceType(device.type, device.tpid);

    briPorts.push({
        Enable: 1,
        Name: device['ifname'],
        Alias: `cpe-${device['.name']}`,
        TPID: tpid,
        PVID: device.pvid ? Number(device.vid): 1,
        Type: portType,
        LowerLayers: `Device.Ethernet.Interface.${ethIndex + 1}`,
        _key: device['.name'],
    });

    briVLANPort.push({
        Enable: 1,
        VLAN: `Device.Bridging.Bridge.${bridgeIndex}.VLAN.${vlanIndex}`,
        Port: `Device.Bridging.Bridge.${bridgeIndex}.Port.${briPorts.length}`,
        Untagged: device.untagged ? 1 : 0,
        _key: device['.name'],
    });

    if (qinqDev && qinqDev.vid) {
        createProviderBridge(qinqDev, 'PE', ethIndex + 1, bridges, providerBridges, bridgeIndex);
    }
}

function importBridge(dev, devices, bridges, bridgeVlans, providerBridges) {
    const briPorts = [];
    const briVLAN = [];
    const briVLANPort = [];

    // create the management port first
    briPorts.push({
        Alias: `cpe-${dev.name}`,
        Enable: 1,
        Name: dev.name,
        ManagementPort: 1,
        PVID: 1,
        TPID: 37120,
        Type: 'VLANUnawarePort',
        PriorityRegeneration: ''
    });

    bridges.push({
        Name: dev.name,
        Alias: `cpe-${dev.name}`,
        Enable: 1,
        'Port.': briPorts,
        'VLAN.': briVLAN,
        'VLANPort.': briVLANPort,
        _key: dev['.name'],
    });

    const ethPorts = devices.filter(x => x.type === undefined && x.eee !== undefined);

    for (const portName of (dev.ports || [])) {
        let device;
        const portIndex = ethPorts.findIndex(x => x.ifname === portName);
        const briVLANInfo = findPortFromBridgeVlan(bridgeVlans, portName);
        if (portIndex >= 0 && !briVLANInfo) {
            // Regular ethernet port
            const ethDevice = ethPorts[portIndex];
            addRegularEthernetPort(ethDevice, portIndex, briPorts);
            continue;
        }

        if (briVLANInfo && portIndex >= 0) {
            // bridge-vlan device
            device = {['.name']: portName, ifname: portName, type: '8021q', name: portName, vid: Number(briVLANInfo[0]), untagged: !briVLANInfo[1], pvid: briVLANInfo[2]};
        } else {
            // vlan device
            device = devices.find(x => x.name === portName);
            if (!device) {
                // check if it is a valid sub-interface
                if (isVLANSubInterface(portName, ethPorts)) {
                    const ifname = portName.split('.').slice(0, -1).join('.');
                    const vid = portName.split('.').pop();
                    device = {['.name']: portName, ifname: ifname, type: '8021q', name: portName, vid: Number(vid)};
                } else {
                    _log_error('device not found', portName);
                    return;
                }
            }
        }

        if (!device.ifname || !device.vid) {
            _log_error('ifname or vid not found', device['.name']);
            return;
        }

        handleVlanDevice(bridges.length, device, ethPorts, devices, bridges, briPorts, briVLAN, briVLANPort, providerBridges);
    }
}

export function importDeviceBridgingBridge() {
    const bridges = [];
    const providerBridges = [];
    const devices = getUciByType('network', 'device');
    const bridgeVlans = getUciByType('network', 'bridge-vlan');
    devices?.forEach(dev => {
        if (dev.type === 'bridge') {
            const bridgeVlan = bridgeVlans?.filter(x => x.device === dev.name);
            importBridge(dev, devices, bridges, bridgeVlan, providerBridges);
        }
    });

    if (providerBridges.length > 0) {
        _dm_update('Device.Bridging.ProviderBridge.', providerBridges);
    }

    return bridges;
}
