package api

import (
	"fmt"
	"testing"

	"clicd/internal/config"
)

func TestDetectReflectionAbuseIgnoresSingleDNSResolver(t *testing.T) {
	resetSecurityTestConfig()

	stats := newTrafficStats()
	for i := 0; i < 180; i++ {
		stats.add(connEntry{
			dstIP:   "1.1.1.1",
			dstPort: 53,
			proto:   "udp",
			state:   "UNREPLIED",
		})
	}

	ss := newSecurityScanner()
	ss.detectReflectionAbuse("ct-dns", "10.0.0.2", stats)

	if len(ss.alerts) != 0 {
		t.Fatalf("normal DNS queries to one resolver should not trigger reflection alert: %+v", ss.alerts)
	}
}

func TestDetectReflectionAbuseFlagsWideDNSFanout(t *testing.T) {
	resetSecurityTestConfig()

	stats := newTrafficStats()
	for i := 0; i < 120; i++ {
		stats.add(connEntry{
			dstIP:   fmt.Sprintf("203.0.113.%d", i),
			dstPort: 53,
			proto:   "udp",
			state:   "UNREPLIED",
		})
	}

	ss := newSecurityScanner()
	ss.detectReflectionAbuse("ct-dns", "10.0.0.2", stats)

	if len(ss.alerts) != 1 {
		t.Fatalf("expected one reflection alert, got %+v", ss.alerts)
	}
	if got := ss.alerts[0].Type; got != "reflection" {
		t.Fatalf("expected reflection alert, got %q", got)
	}
}

func TestDetectPortScansUsesHalfOpenConnections(t *testing.T) {
	resetSecurityTestConfig()

	established := newTrafficStats()
	for port := 8000; port < 8020; port++ {
		established.add(connEntry{
			dstIP:   "198.51.100.10",
			dstPort: port,
			proto:   "tcp",
			state:   "ESTABLISHED",
		})
	}

	ss := newSecurityScanner()
	ss.detectPortScans("ct-web", "10.0.0.3", established)
	if len(ss.alerts) != 0 {
		t.Fatalf("established multi-port connections should not trigger port scan alert: %+v", ss.alerts)
	}

	halfOpen := newTrafficStats()
	for port := 8000; port < 8012; port++ {
		halfOpen.add(connEntry{
			dstIP:   "198.51.100.10",
			dstPort: port,
			proto:   "tcp",
			state:   "SYN_SENT",
		})
	}

	ss.detectPortScans("ct-web", "10.0.0.3", halfOpen)
	if len(ss.alerts) != 1 {
		t.Fatalf("expected one port scan alert, got %+v", ss.alerts)
	}
	if got := ss.alerts[0].Type; got != "port_scan" {
		t.Fatalf("expected port_scan alert, got %q", got)
	}
}

func TestCancelPendingSecurityStops(t *testing.T) {
	resetSecurityTestConfig()

	q := &TaskQueue{
		tasks: map[string]*Task{},
	}
	securityTask := &Task{
		ID:          "task-1",
		Type:        TaskStop,
		ContainerID: 1,
		Status:      "pending",
		User:        "system:security",
	}
	userTask := &Task{
		ID:          "task-2",
		Type:        TaskStop,
		ContainerID: 2,
		Status:      "pending",
		User:        "admin",
	}
	runningSecurityTask := &Task{
		ID:          "task-3",
		Type:        TaskStop,
		ContainerID: 3,
		Status:      "running",
		User:        "system:security",
	}
	q.tasks[securityTask.ID] = securityTask
	q.tasks[userTask.ID] = userTask
	q.tasks[runningSecurityTask.ID] = runningSecurityTask
	q.opQueue = []*Task{securityTask, userTask, runningSecurityTask}

	if got := q.CancelPendingSecurityStops(); got != 1 {
		t.Fatalf("expected one pending security stop to be cancelled, got %d", got)
	}
	if _, ok := q.tasks[securityTask.ID]; ok {
		t.Fatal("pending security stop task was not removed")
	}
	if _, ok := q.tasks[userTask.ID]; !ok {
		t.Fatal("user stop task should not be removed")
	}
	if _, ok := q.tasks[runningSecurityTask.ID]; !ok {
		t.Fatal("running security stop task should be left for worker-side skip")
	}
	if len(q.opQueue) != 2 {
		t.Fatalf("expected op queue to keep two tasks, got %d", len(q.opQueue))
	}
}

func resetSecurityTestConfig() {
	config.AppConfig = &config.ClicdConfig{
		Containers: []config.Container{},
		AuditLogs:  []config.AuditLog{},
		Tasks:      []config.SavedTask{},
	}
}
