/*
 * 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 {
    getUciOption, getUciByType, setUci, addUci, delUci
} from '../uci.js';
import { getParamValue, replaceArrayElement, isTrue } from '../utils.js';
import * as dm from './dm_consts.js';
import { getBridgeDeviceType, getTPIDFromDeviceType } from './common.js';

function findVLANPort(vlanPort, VLANs, Ports) {
    const [, vlanIndices] = _dm_node(vlanPort.VLAN);
    const vlanIdx = vlanIndices[vlanIndices.length - 1];
    const vlan = VLANs?.find(x => x['.index'] === vlanIdx);
    if (!vlan) {
        _log_error(`vlan not found for vlanPort: ${vlanPort.VLAN}`);
        return;
    }

    const [, portIndices] = _dm_node(vlanPort.Port);
    const portIdx = portIndices[portIndices.length - 1];
    const port = Ports?.find(x => x['.index'] === portIdx);
    if (!port) {
        _log_error(`port not found for vlanPort: ${vlanPort.Port}`);
        return;
    }

    return [vlan, port];
}

function createVLANDevice(devName, ifname, VLAN, Port) {
    const ingress_qos_mapping = Port.PriorityRegeneration !== '0,1,2,3,4,5,6,7'
            ? Port.PriorityRegeneration.split(',').map((p, i) => `${i}:${p}`)
            : '';

    const egress_qos_mapping = Port.X_IOPSYS_EU_EgressPriorityRegeneration
        ? Port.X_IOPSYS_EU_EgressPriorityRegeneration.split(',').map((p, i) => `${i}:${p}`)
        : '';

    const uciConfigs = {
        ifname: ifname,
        vid: VLAN.VLANID,
        name: ifname + '.' + VLAN.VLANID,
        type: getBridgeDeviceType(Port.Type),
        tpid: getTPIDFromDeviceType(Port.Type, Port.TPID),
        ingress_qos_mapping,
        egress_qos_mapping,
    };
    addUci('network', 'device', devName, uciConfigs);
}

function applyBridge(bri, Ports, VLANs, VLANPorts) {
    deinitDeviceBridgingBridge(bri._key, false);

    const ports = [];

    for (const vlan of VLANs || []) {
        vlan.ports = [];
        vlan.hasUntagged = false;
    }

    for (const vlanPort of VLANPorts || []) {
        if (!vlanPort.Enable || !vlanPort.Port || !vlanPort.VLAN) {
            continue;
        }

        const [vlan, port] = findVLANPort(vlanPort, VLANs, Ports);
        if (!vlan || !port || port.LowerLayers === '' || !port.Enable || !vlan.Enable || vlan.VLANID <= 0) {
            continue;
        }

        port.used = true;

        if (port.Type === 'ProviderNetworkPort') {
            continue;
        }

        const devName = `br_${bri['.index']}_dev_${vlanPort['.index']}`;
        if (!port.LowerLayers.startsWith('Device.Ethernet.Interface')) {
            _log_error(`applyBridge, LowerLayers not found for port: ${port.LowerLayers}`);
            continue;
        }

        const ifname = _dm_linker_value(port.LowerLayers);
        if (!ifname) {
            _log_error(`applyBridge, ifname not found for port: ${port.LowerLayers}`);
            continue;
        }

        if (vlanPort.Untagged) {
            ports.push(ifname);
            vlan.hasUntagged = true;
            vlan.ports.push(ifname + ':u' + (vlanPort.PVID === vlan.VLANID ? '*' : ''));
            // vlan.ports.push(ifname + ':u*');
        } else {
            createVLANDevice(devName, ifname, vlan, port);
            const vlanDevName = ifname + '.' + vlan.VLANID;
            ports.push(vlanDevName);
            vlan.ports.push(vlanDevName + ':u*');
        }
    }

    for (const port of Ports || []) {
        if (port.used || isTrue(port.ManagementPort)) {
            continue;
        }
        if (port.LowerLayers.startsWith('Device.Ethernet.Interface')) {
            const ifname = _dm_linker_value(port.LowerLayers);
            if (!ifname) {
                _log_error(`applyBridge, ifname not found for port: ${port.LowerLayers}`);
                continue;
            }
            ports.push(ifname);
        }
    }

    if (ports.length > 0) {
        setUci('network', bri._key, { ports: ports });
    }

    // create the bridge-vlan for the untagged port
    for (const vlan of VLANs || []) {
        if (vlan.hasUntagged) {
            addUci('network', 'bridge-vlan', `br_${bri['.index']}_bv_${vlan['.index']}`, {
                device: bri.Name,
                vlan: vlan.VLANID,
                ports: vlan.ports,
            });
        }
    }

    applyProviderBridges();
}

function applyPEBridges(ifname, vlanID, portLowerLayers, cvlanBridgePath) {const vlanPorts = _dm_get(cvlanBridgePath + '.VLANPort.');
    for (const vlanPort of vlanPorts || []) {
        if (!vlanPort.Enable || !vlanPort.Port || !vlanPort.VLAN) {
            continue;
        }
        const portVals = _dm_get(vlanPort.Port);
        if (portVals?.LowerLayers !== portLowerLayers || portVals?.Type !== 'CustomerEdgePort') {
            _log_error(`applyPEBridges, portVals not found for vlanPort: ${vlanPort.Port}`);
            continue;
        }

        const vlan = _dm_get(vlanPort.VLAN);
        if (!vlan || vlan.VLANID <= 0 || !vlan.Enable) {
            _log_error(`applyPEBridges, vlan not found for vlanPort: ${vlanPort.VLAN}`);
            continue;
        }

        const devName = ifname + '.' + vlan.VLANID;

        const briName = getParamValue(cvlanBridgePath, '_key');
        if (!briName) {
            _log_error(`applyPEBridges, briName not found for cvlanBridgePath: ${cvlanBridgePath}`);
            continue;
        }

        const ports = getUciOption('network', briName, 'ports') || [];
        const devs = getUciByType('network', 'device', { match: { name: devName } });
        if (devs.length === 0) {
            _log_error(`applyPEBridges, device not found for devName: ${devName}`);
            continue;
        }

        const newName = `${ifname}.${vlanID}.${vlan.VLANID}`;
        setUci('network', devs[0]['.name'], {ifname: `${ifname}.${vlanID}`, name: newName});
        replaceArrayElement(ports, devName, newName);
        setUci('network', briName, { ports: ports });
    }
}

function applyProviderBridge(pbridgeIndex, type, svlanBridgePath, cvlanBridgePaths) {
    const vlanPorts = _dm_get(svlanBridgePath + '.VLANPort.');
    const briName = getParamValue(svlanBridgePath, '_key');
    if (briName) {
        delUci('network', briName);
    }
    for (const vlanPort of vlanPorts || []) {
        if (!vlanPort.Enable || !vlanPort.Port || !vlanPort.VLAN) {
            continue;
        }

        const portVals = _dm_get(vlanPort.Port);
        if (!portVals) {
            _log_error(`applyProviderBridge, portVals not found for vlanPort: ${vlanPort.Port}`);
            continue;
        }

        if (portVals.Type !== 'ProviderNetworkPort') {
            _log_error(`applyProviderBridge, portVals.Type is not ProviderNetworkPort for vlanPort: ${vlanPort.Port}`);
            continue;
        }

        const ifname = _dm_linker_value(portVals.LowerLayers);
        if (!ifname) {
            _log_error(`applyProviderBridge, ifname not found for port: ${portVals.LowerLayers}`);
            continue;
        }

        const vlan = _dm_get(vlanPort.VLAN);
        if (!vlan || !vlan.Enable || vlan.VLANID <= 0) {
            _log_error(`applyProviderBridge, vlan invalid for vlanPort: ${vlanPort.VLAN}`);
            continue;
        }

        const devName = `pb_${pbridgeIndex}_dev_${vlanPort['.index']}`;
        createVLANDevice(devName, ifname, vlan, portVals);

        cvlanBridgePaths.split(',').forEach(cvlanBridgePath => {
            if (type === 'S-VLAN') {
                const briName = getParamValue(cvlanBridgePath, '_key');
                if (briName) {
                    let ports = getUciOption('network', briName, 'ports') || [];
                    ports = ports.filter(x => !x.startsWith(ifname));
                    ports.push(ifname + '.' + vlan.VLANID);
                    setUci('network', briName, { ports: ports });
                } else {
                    _log_error(`applyProviderBridge, briName not found for cvlanBridgePath: ${cvlanBridgePath}`);
                }
            } else {
                applyPEBridges(ifname, vlan.VLANID, portVals.LowerLayers, cvlanBridgePath);
            }
        });

    }
}

function applyProviderBridges() {
    const pbridges = _dm_get(dm.DM_DEVICE_BRIDGING_PROVIDERBRIDGE);
    for (const pbridge of pbridges || []) {
        if (!pbridge.Enable || !pbridge.SVLANcomponent || !pbridge.CVLANcomponents || !pbridge.Type) {
            continue;
        }

        applyProviderBridge(pbridge['.index'], pbridge.Type, pbridge.SVLANcomponent, pbridge.CVLANcomponents);
    }
}

function applyAllBridges() {
    const bridges = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE);
    for (const bri of bridges || []) {
        applyBridge(bri, bri.Port, bri.VLAN, bri.VLANPort);
    }
}

export function applyDeviceBridgingProviderBridge() {
    applyAllBridges();
}

function isProviderBridge(ports) {
    return ports.some(port => port.Type === 'ProviderNetworkPort' || port.Type === 'CustomerEdgePort');
}

export function applyDeviceBridgingBridgePort(ports, bri) {
    const vlans = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_VLAN, bri['.index']);
    const vlanPorts = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_VLANPORT, bri['.index']);
    if (isProviderBridge(ports)) {
        applyAllBridges();
        return;
    }
    applyBridge(bri, ports, vlans, vlanPorts);
}

export function applyDeviceBridgingBridgeVLAN(vlans, bri) {
    const ports = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_PORT, bri['.index']);
    if (isProviderBridge(ports)) {
        applyAllBridges();
        return;
    }
    const vlanPorts = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_VLANPORT, bri['.index']);
    applyBridge(bri, ports, vlans, vlanPorts);
}

export function applyDeviceBridgingBridgeVLANPort(vlanPorts, bri) {
    const ports = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_PORT, bri['.index']);
    if (isProviderBridge(ports)) {
        applyAllBridges();
        return;
    }
    const vlans = _dm_get(dm.DM_DEVICE_BRIDGING_BRIDGE_VLAN, bri['.index']);
    applyBridge(bri, ports, vlans, vlanPorts);
}

export function initDeviceBridgingBridge(bri) {
    setUci('network', bri._key, {
        type: 'bridge',
        name: bri.Name,
        enabled: '0',
    });
    // create empty interface for the bridge
    addUci('network', 'interface', `itf_${bri._key}`, {
        device: bri.Name,
        bridge_empty: '1',
    });
}

export const filterDeviceBridgingBridge = uci => uci.type === 'bridge';

function delVLANDevice(devName, devices, ethPorts) {
    if (ethPorts.find(x => x.name === devName))  {
        return;
    }

    const dev = devices.find(x => x.name === devName);
    if (!dev) {
        return;
    }

    // delete possible vlan stack device
    delVLANDevice(dev.ifname, devices, ethPorts);
    delUci('network', dev['.name']);
}

export function deinitDeviceBridgingBridge(uci, removeInterface = true) {
    const ports = getUciOption('network', uci, 'ports');
    const devices = getUciByType('network', 'device');

    const ethPorts = devices.filter(x => x.type === undefined && x.eee !== undefined);
    ports?.forEach(port => {
        delVLANDevice(port, devices, ethPorts);
    });

    const name = getUciOption('network', uci, 'name');
    // delete related bridge-vlan devices (query first to avoid noisy "section missing" logs)
    const bridgeVlans = getUciByType('network', 'bridge-vlan', { match: { device: name } }) || [];
    bridgeVlans.forEach(vlan => delUci('network', vlan['.name']));

    if (removeInterface) {
        // delete the empty interface created for this bridge (query first)
        const interfaces = getUciByType('network', 'interface', { match: { device: name, bridge_empty: '1' } }) || [];
        interfaces.forEach(intf => delUci('network', intf['.name']));
    }
}
