package netstack

import (
	"context"
	"errors"
	"path/filepath"
	"slices"
	"strconv"
	"sync"
	"testing"
)

func testCachedAttachment(id int, networkName, ifName string) cniAttachment {
	return cniAttachment{
		ContainerID:   cniContainerIDPrefix + strconv.Itoa(id),
		NetworkName:   networkName,
		InterfaceName: ifName,
		NetNS:         filepath.Join(networkNamespaceDir, networkNamespacePrefix+strconv.Itoa(id)),
	}
}

func TestReconcileStaleCacheRemovesOnlyCurrentConchAttachments(t *testing.T) {
	const bridgeNetwork = "conch-test"
	attachments := []cniAttachment{
		testCachedAttachment(2, bridgeNetwork, cniOuterInterfaceName),
		testCachedAttachment(3, bridgeNetwork, cniOuterInterfaceName),
	}
	historical := testCachedAttachment(4, bridgeNetwork, cniOuterInterfaceName)
	historical.NetNS = "/var/run/netns/ns-4"
	attachments = append(attachments, historical, testCachedAttachment(5, "other-network", cniOuterInterfaceName))

	var mu sync.Mutex
	var removed []string
	manager := &CNIManager{
		backend: &fakeCNIBackend{
			attachments: func() ([]cniAttachment, error) { return attachments, nil },
			remove: func(_ context.Context, id, path string) error {
				if path != "" {
					t.Errorf("Remove(%q) path = %q, want empty", id, path)
				}
				mu.Lock()
				removed = append(removed, id)
				mu.Unlock()
				return nil
			},
		},
		ifName:            cniOuterInterfaceName,
		bridgeNetworkName: bridgeNetwork,
	}

	count, err := manager.reconcileStaleCache(context.Background())
	if err != nil {
		t.Fatalf("reconcileStaleCache(): %v", err)
	}
	if count != 2 {
		t.Fatalf("reconcileStaleCache() count = %d, want 2", count)
	}
	mu.Lock()
	slices.Sort(removed)
	gotRemoved := append([]string(nil), removed...)
	mu.Unlock()
	if !slices.Equal(gotRemoved, []string{"conch-slot-2", "conch-slot-3"}) {
		t.Fatalf("removed attachments = %v", gotRemoved)
	}
}

func TestReconcileStaleCacheDoesNotRetryFailedCNIDel(t *testing.T) {
	const bridgeNetwork = "conch-test"
	wantErr := errors.New("device or resource busy")
	removeCalls := 0
	manager := &CNIManager{
		backend: &fakeCNIBackend{
			attachments: func() ([]cniAttachment, error) {
				return []cniAttachment{testCachedAttachment(6, bridgeNetwork, cniOuterInterfaceName)}, nil
			},
			remove: func(context.Context, string, string) error {
				removeCalls++
				return wantErr
			},
		},
		ifName:            cniOuterInterfaceName,
		bridgeNetworkName: bridgeNetwork,
	}

	count, err := manager.reconcileStaleCache(context.Background())
	if count != 1 || !errors.Is(err, wantErr) {
		t.Fatalf("reconcileStaleCache() = (%d, %v), want (1, %v)", count, err, wantErr)
	}
	if removeCalls != 1 {
		t.Fatalf("CNI Remove calls = %d, want 1", removeCalls)
	}
}

func TestReconcileStaleCacheReportsAttachmentEnumerationFailure(t *testing.T) {
	wantErr := errors.New("cache unavailable")
	manager := &CNIManager{
		backend:           &fakeCNIBackend{attachments: func() ([]cniAttachment, error) { return nil, wantErr }},
		ifName:            cniOuterInterfaceName,
		bridgeNetworkName: "conch-test",
	}

	count, err := manager.reconcileStaleCache(context.Background())
	if count != 0 || !errors.Is(err, wantErr) {
		t.Fatalf("reconcileStaleCache() = (%d, %v), want (0, %v)", count, err, wantErr)
	}
}