// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2026 Robin Jarry

#include "event.h"
#include "l2.h"
#include "log.h"
#include "vrf.h"

#include <rte_ether.h>

LOG_TYPE("bridge");

static int
bridge_all_member_add_mac(const struct iface_info_bridge *br, const struct rte_ether_addr *mac) {
	for (uint16_t i = 0; i < br->n_members; i++) {
		if (iface_add_eth_addr(br->members[i], mac) < 0)
			return errno_set(errno);
	}
	return 0;
}

static int
bridge_all_member_del_mac(const struct iface_info_bridge *br, const struct rte_ether_addr *mac) {
	for (uint16_t i = 0; i < br->n_members; i++) {
		if (iface_del_eth_addr(br->members[i], mac) < 0 && errno != ENOENT)
			return errno_set(errno);
	}
	return 0;
}

static int bridge_mac_add(struct iface *iface, struct iface_mac *m) {
	return bridge_all_member_add_mac(iface_info_bridge(iface), &m->mac);
}

static int bridge_mac_del(struct iface *iface, struct iface_mac *m) {
	return bridge_all_member_del_mac(iface_info_bridge(iface), &m->mac);
}

static int bridge_reconfig(
	struct iface *iface,
	uint64_t set_attrs,
	const struct gr_iface *,
	const void *api_info
) {
	struct iface_info_bridge *cur = iface_info_bridge(iface);
	const struct gr_iface_info_bridge *next = api_info;

	if (next->flags & ~GR_BRIDGE_F_VALID)
		return errno_set(EINVAL);

	if (set_attrs & GR_BRIDGE_SET_MAC)
		iface_set_eth_addr(iface, &next->mac);
	if (set_attrs & GR_BRIDGE_SET_FLAGS)
		cur->flags = next->flags;
	if (set_attrs & GR_BRIDGE_SET_AGEING_TIME)
		cur->ageing_time = next->ageing_time ?: GR_BRIDGE_DEFAULT_AGEING;

	return 0;
}

static int bridge_attach_member(struct iface *bridge, struct iface *member) {
	struct iface_info_bridge *br = iface_info_bridge(bridge);

	switch (member->type) {
	case GR_IFACE_TYPE_PORT:
	case GR_IFACE_TYPE_VLAN:
	case GR_IFACE_TYPE_BOND:
	case GR_IFACE_TYPE_VXLAN:
		break;
	default:
		return errno_set(EMEDIUMTYPE);
	}

	for (unsigned i = 0; i < br->n_members; i++) {
		if (br->members[i] == member)
			return 0; // already a member
	}

	if (br->n_members == ARRAY_DIM(br->members))
		return errno_set(EUSERS);

	if (iface_set_promisc(member, true) < 0 && errno != EOPNOTSUPP)
		return errno_log(errno, "iface_set_promisc(member)");

	vec_foreach_ref (struct iface_mac *m, bridge->macs) {
		if (iface_add_eth_addr(member, &m->mac) < 0)
			return errno_log(errno, "iface_add_eth_addr(member)");
	}
	iface_add_eth_addr(member, &br->mac);

	br->members[br->n_members++] = member;
	member->domain_id = bridge->id;
	member->vrf_id = GR_VRF_ID_UNDEF;
	member->mode = GR_IFACE_MODE_BRIDGE;

	fdb_sync_hardware(bridge, member, true);

	return 0;
}

static int bridge_detach_member(struct iface *bridge, struct iface *member) {
	struct iface_info_bridge *br = iface_info_bridge(bridge);

	for (unsigned i = 0; i < br->n_members; i++) {
		if (br->members[i] == member) {
			unsigned last = br->n_members - 1;

			fdb_sync_hardware(bridge, member, false);
			vec_foreach_ref (struct iface_mac *m, bridge->macs)
				iface_del_eth_addr(member, &m->mac);
			iface_del_eth_addr(member, &br->mac);

			if (i < last)
				br->members[i] = br->members[last];
			br->n_members--;
			iface_set_promisc(member, false);
			member->domain_id = GR_IFACE_ID_UNDEF;
			member->mode = GR_IFACE_MODE_VRF;
			fdb_purge_iface(member->id);
			break;
		}
	}

	return 0;
}

static int bridge_fini(struct iface *iface) {
	struct iface_info_bridge *bridge = iface_info_bridge(iface);

	for (unsigned i = 0; i < bridge->n_members; i++) {
		struct iface *member = bridge->members[i];
		fdb_sync_hardware(iface, member, false);
		vec_foreach_ref (struct iface_mac *m, iface->macs)
			iface_del_eth_addr(member, &m->mac);
		iface_del_eth_addr(member, &bridge->mac);
		iface_set_promisc(member, false);
		member->vrf_id = vrf_default_get_or_create();
		if (member->vrf_id != GR_VRF_ID_UNDEF)
			vrf_incref(member->vrf_id);
		member->domain_id = GR_IFACE_ID_UNDEF;
		member->mode = GR_IFACE_MODE_VRF;
		event_push(GR_EVENT_IFACE_POST_RECONFIG, member);
	}

	return 0;
}

static int bridge_init(struct iface *iface, const void *api_info) {
	int ret;

	iface->domain_id = iface->id; // for convenience, bridges are in their own domain

	ret = bridge_reconfig(iface, IFACE_SET_ALL, NULL, api_info);
	if (ret < 0) {
		bridge_fini(iface);
		errno = -ret;
	}

	return ret;
}

static int bridge_get_eth_addr(const struct iface *iface, struct rte_ether_addr *mac) {
	const struct iface_info_bridge *bridge = iface_info_bridge(iface);
	*mac = bridge->mac;
	return 0;
}

static int bridge_set_eth_addr(struct iface *iface, const struct rte_ether_addr *mac) {
	struct iface_info_bridge *bridge = iface_info_bridge(iface);
	struct rte_ether_addr new_mac;
	int ret;

	if (rte_is_zero_ether_addr(mac)) {
		rte_eth_random_addr(new_mac.addr_bytes);
	} else if (rte_is_same_ether_addr(mac, &bridge->mac)) {
		return 0;
	} else {
		new_mac = *mac;
	}

	if (!rte_is_zero_ether_addr(&bridge->mac)) {
		if ((ret = bridge_all_member_del_mac(bridge, &bridge->mac)) < 0)
			return ret;
	}
	if ((ret = bridge_all_member_add_mac(bridge, &new_mac)) < 0)
		return ret;

	bridge->mac = new_mac;

	return 0;
}

static void bridge_to_api(void *info, const struct iface *iface) {
	const struct iface_info_bridge *bridge = iface_info_bridge(iface);
	struct gr_iface_info_bridge *api = info;

	api->ageing_time = bridge->ageing_time;
	api->flags = bridge->flags;
	api->mac = bridge->mac;
	api->n_members = bridge->n_members;
	for (unsigned i = 0; i < bridge->n_members; i++)
		api->members[i] = bridge->members[i]->id;
}

static struct iface_type iface_type_bridge = {
	.id = GR_IFACE_TYPE_BRIDGE,
	.pub_size = sizeof(struct gr_iface_info_bridge),
	.priv_size = sizeof(struct iface_info_bridge),
	.init = bridge_init,
	.reconfig = bridge_reconfig,
	.fini = bridge_fini,
	.attach_domain = bridge_attach_member,
	.detach_domain = bridge_detach_member,
	.get_eth_addr = bridge_get_eth_addr,
	.set_eth_addr = bridge_set_eth_addr,
	.add_eth_addr = bridge_mac_add,
	.del_eth_addr = bridge_mac_del,
	.to_api = bridge_to_api,
};

RTE_INIT(bridge_constructor) {
	iface_type_register(&iface_type_bridge);
}
