package policy

import (
	"net"
	"testing"

	"github.com/sandbox0-ai/sandbox0/manager/pkg/apis/sandbox0/v1alpha1"
)

func TestMatchEgressAuthRuleMatchesHTTPRule(t *testing.T) {
	p := &CompiledPolicy{
		Egress: CompiledRuleSet{
			AuthRules: []CompiledEgressAuthRule{
				{
					Name:     "example-http",
					AuthRef:  "example-api",
					Protocol: v1alpha1.EgressAuthProtocolHTTP,
					Domains: []DomainRule{
						{Pattern: "api.example.com", Type: DomainMatchExact},
					},
					Ports: []PortRange{
						{Protocol: "tcp", Start: 80, End: 80},
					},
				},
			},
		},
	}

	rule := MatchEgressAuthRule(p, "tcp", "http", 80, "api.example.com")
	if rule == nil {
		t.Fatal("expected auth rule match")
	}
	if rule.AuthRef != "example-api" {
		t.Fatalf("unexpected auth ref %q", rule.AuthRef)
	}
}

func TestMatchEgressAuthRuleSkipsTLSRuleForHTTPClassifier(t *testing.T) {
	p := &CompiledPolicy{
		Egress: CompiledRuleSet{
			AuthRules: []CompiledEgressAuthRule{
				{
					Name:     "example-https",
					AuthRef:  "example-api",
					Protocol: v1alpha1.EgressAuthProtocolHTTPS,
					Domains: []DomainRule{
						{Pattern: "api.example.com", Type: DomainMatchExact},
					},
				},
			},
		},
	}

	if rule := MatchEgressAuthRule(p, "tcp", "http", 443, "api.example.com"); rule != nil {
		t.Fatalf("expected no auth rule, got %+v", rule)
	}
}

func TestMatchEgressAuthRuleMatchesGRPCRuleForTLSClassifier(t *testing.T) {
	p := &CompiledPolicy{
		Egress: CompiledRuleSet{
			AuthRules: []CompiledEgressAuthRule{
				{
					Name:     "example-grpc",
					AuthRef:  "example-api",
					Protocol: v1alpha1.EgressAuthProtocolGRPC,
					TLSMode:  v1alpha1.EgressTLSModeTerminateReoriginate,
					Domains: []DomainRule{
						{Pattern: "api.example.com", Type: DomainMatchExact},
					},
					Ports: []PortRange{
						{Protocol: "tcp", Start: 443, End: 443},
					},
				},
			},
		},
	}

	rule := MatchEgressAuthRule(p, "tcp", "tls", 443, "api.example.com")
	if rule == nil {
		t.Fatal("expected grpc auth rule match for tls classifier")
	}
	if rule.AuthRef != "example-api" {
		t.Fatalf("unexpected auth ref %q", rule.AuthRef)
	}
}

func TestMatchEgressAuthRuleSkipsDisabledRolloutRule(t *testing.T) {
	p := &CompiledPolicy{
		Egress: CompiledRuleSet{
			AuthRules: []CompiledEgressAuthRule{
				{
					Name:     "example-http",
					AuthRef:  "example-api",
					Rollout:  v1alpha1.EgressAuthRolloutDisabled,
					Protocol: v1alpha1.EgressAuthProtocolHTTP,
					Domains: []DomainRule{
						{Pattern: "api.example.com", Type: DomainMatchExact},
					},
				},
			},
		},
	}

	if rule := MatchEgressAuthRule(p, "tcp", "http", 80, "api.example.com"); rule != nil {
		t.Fatalf("expected disabled rollout rule to be ignored, got %+v", rule)
	}
}

func TestMatchEgressAuthRuleMatchesTLSRuleForTLSClassifier(t *testing.T) {
	p := &CompiledPolicy{
		Egress: CompiledRuleSet{
			AuthRules: []CompiledEgressAuthRule{
				{
					Name:     "example-mtls",
					AuthRef:  "example-cert",
					Protocol: v1alpha1.EgressAuthProtocolTLS,
					TLSMode:  v1alpha1.EgressTLSModeTerminateReoriginate,
					Domains: []DomainRule{
						{Pattern: "db.example.com", Type: DomainMatchExact},
					},
					Ports: []PortRange{
						{Protocol: "tcp", Start: 5432, End: 5432},
					},
				},
			},
		},
	}

	rule := MatchEgressAuthRule(p, "tcp", "tls", 5432, "db.example.com")
	if rule == nil {
		t.Fatal("expected tls auth rule match for tls classifier")
	}
	if rule.AuthRef != "example-cert" {
		t.Fatalf("unexpected auth ref %q", rule.AuthRef)
	}
}

func TestMatchEgressAuthRuleMatchesSOCKS5Rule(t *testing.T) {
	p := &CompiledPolicy{
		Egress: CompiledRuleSet{
			AuthRules: []CompiledEgressAuthRule{{
				Name:     "corp-socks",
				AuthRef:  "proxy-cred",
				Protocol: v1alpha1.EgressAuthProtocolSOCKS5,
				Ports: []PortRange{
					{Protocol: "tcp", Start: 1080, End: 1080},
				},
			}},
		},
	}

	rule := MatchEgressAuthRule(p, "tcp", "socks5", 1080, "")
	if rule == nil || rule.AuthRef != "proxy-cred" {
		t.Fatalf("unexpected socks5 rule match: %+v", rule)
	}
}

func TestMatchEgressAuthRuleMatchesSSHRule(t *testing.T) {
	p := &CompiledPolicy{
		Egress: CompiledRuleSet{
			AuthRules: []CompiledEgressAuthRule{{
				Name:     "git-ssh",
				AuthRef:  "git-cred",
				Protocol: v1alpha1.EgressAuthProtocolSSH,
				Domains: []DomainRule{
					{Pattern: "github.com", Type: DomainMatchExact},
				},
				Ports: []PortRange{
					{Protocol: "tcp", Start: 22, End: 22},
				},
			}},
		},
	}

	rule := MatchEgressAuthRule(p, "tcp", "ssh", 22, "github.com")
	if rule == nil || rule.AuthRef != "git-cred" {
		t.Fatalf("unexpected ssh rule match: %+v", rule)
	}
}

func TestMatchEgressAuthRuleMatchesMQTTRule(t *testing.T) {
	p := &CompiledPolicy{
		Egress: CompiledRuleSet{
			AuthRules: []CompiledEgressAuthRule{{
				Name:     "broker-auth",
				AuthRef:  "mqtt-cred",
				Protocol: v1alpha1.EgressAuthProtocolMQTT,
				Domains: []DomainRule{
					{Pattern: "broker.example.com", Type: DomainMatchExact},
				},
			}},
		},
	}

	rule := MatchEgressAuthRule(p, "tcp", "mqtt", 1883, "broker.example.com")
	if rule == nil || rule.AuthRef != "mqtt-cred" {
		t.Fatalf("unexpected mqtt rule match: %+v", rule)
	}
}

func TestMatchEgressAuthRuleMatchesRedisRule(t *testing.T) {
	p := &CompiledPolicy{
		Egress: CompiledRuleSet{
			AuthRules: []CompiledEgressAuthRule{{
				Name:     "redis-auth",
				AuthRef:  "redis-cred",
				Protocol: v1alpha1.EgressAuthProtocolRedis,
				Ports: []PortRange{
					{Protocol: "tcp", Start: 6379, End: 6379},
				},
			}},
		},
	}

	rule := MatchEgressAuthRule(p, "tcp", "redis", 6379, "")
	if rule == nil || rule.AuthRef != "redis-cred" {
		t.Fatalf("unexpected redis rule match: %+v", rule)
	}
}

func TestCloneRuleSetCopiesAuthRules(t *testing.T) {
	in := CompiledRuleSet{
		AuthRules: []CompiledEgressAuthRule{
			{Name: "example", AuthRef: "ref"},
		},
		AllowedCIDRs: []*net.IPNet{mustTestCIDR("10.0.0.0/24")},
	}

	out := cloneRuleSet(in)
	out.AuthRules[0].AuthRef = "other"

	if in.AuthRules[0].AuthRef != "ref" {
		t.Fatalf("expected auth rules clone to be independent")
	}
}

func mustTestCIDR(value string) *net.IPNet {
	_, cidr, err := net.ParseCIDR(value)
	if err != nil {
		panic(err)
	}
	return cidr
}
