package links

import (
	"context"
	"fmt"

	"github.com/containernetworking/plugins/pkg/ns"
	clabnodesstate "github.com/srl-labs/containerlab/nodes/state"
	"github.com/vishvananda/netlink"
)

type GenericLinkNode struct {
	shortname string
	endpoints []Endpoint
	nspath    string
}

func (g *GenericLinkNode) AddLinkToContainer(
	_ context.Context,
	link netlink.Link,
	f func(ns.NetNS) error,
) error {
	// retrieve the namespace handle
	netns, err := ns.GetNS(g.nspath)
	if err != nil {
		return err
	}
	// move veth endpoint to namespace
	if err := netlink.LinkSetNsFd(link, int(netns.Fd())); err != nil {
		return err
	}
	// execute the given function
	return netns.Do(f)
}

func (g *GenericLinkNode) ExecFunction(_ context.Context, f func(ns.NetNS) error) error {
	// retrieve the namespace handle
	netns, err := ns.GetNS(g.nspath)
	if err != nil {
		return err
	}
	// execute the given function
	return netns.Do(f)
}

func (g *GenericLinkNode) AddEndpoint(e Endpoint) error {
	g.endpoints = append(g.endpoints, e)
	return nil
}

func (g *GenericLinkNode) AdoptEndpoint(e Endpoint) error {
	if e == nil {
		return fmt.Errorf("node %q cannot adopt a nil endpoint", g.shortname)
	}

	owner := e.GetNode()
	if owner == nil {
		return fmt.Errorf(
			"node %q cannot adopt endpoint %q without an owner",
			g.shortname,
			e.GetIfaceName(),
		)
	}

	if owner.GetShortName() != g.shortname {
		return fmt.Errorf(
			"node %q cannot adopt endpoint %q owned by %q",
			g.shortname,
			e.GetIfaceName(),
			owner.GetShortName(),
		)
	}

	for _, owned := range g.endpoints {
		if owned == e {
			return nil
		}
		if owned.GetIfaceName() == e.GetIfaceName() {
			return fmt.Errorf("node %q already tracks interface %q", g.shortname, e.GetIfaceName())
		}
	}

	g.endpoints = append(g.endpoints, e)
	return nil
}

func (g *GenericLinkNode) ReleaseEndpoint(e Endpoint) error {
	for i, ep := range g.endpoints {
		if ep != e {
			continue
		}

		g.endpoints = append(g.endpoints[:i], g.endpoints[i+1:]...)
		return nil
	}

	return fmt.Errorf("node %q does not own endpoint %q", g.shortname, e.GetIfaceName())
}

func (g *GenericLinkNode) GetShortName() string {
	return g.shortname
}

func (g *GenericLinkNode) GetEndpoints() []Endpoint {
	return g.endpoints
}

func (*GenericLinkNode) GetState() clabnodesstate.NodeState {
	// The GenericLinkNode is the basis for Mgmt-Bridge and Host fake node.
	// Both of these do generally exist. Hence the Deployed state in generally returned
	return clabnodesstate.Deployed
}

func (g *GenericLinkNode) Delete(ctx context.Context) error {
	for _, e := range g.endpoints {
		var err error
		if l := e.GetLink(); l != nil {
			err = l.Remove(ctx)
		} else {
			err = e.Remove(ctx)
		}
		if err != nil {
			return err
		}
	}
	return nil
}
