// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0

package cache

import (
	"testing"

	"github.com/projectcapsule/capsule/pkg/api/runtime"
)

func TestCompiledRegexMatchString(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name       string
		expression runtime.ExpressionRegex
		value      string
		want       bool
	}{
		{
			name: "normal expression matches matching value",
			expression: runtime.ExpressionRegex{
				Expression: "trusted/.*",
			},
			value: "trusted/team/app:1",
			want:  true,
		},
		{
			name: "normal expression does not match non matching value",
			expression: runtime.ExpressionRegex{
				Expression: "trusted/.*",
			},
			value: "docker.io/team/app:1",
			want:  false,
		},
		{
			name: "negated expression does not match matching value",
			expression: runtime.ExpressionRegex{
				Expression: "trusted/.*",
				Negate:     true,
			},
			value: "trusted/team/app:1",
			want:  false,
		},
		{
			name: "negated expression matches non matching value",
			expression: runtime.ExpressionRegex{
				Expression: "trusted/.*",
				Negate:     true,
			},
			value: "docker.io/team/app:1",
			want:  true,
		},
	}

	for _, tt := range tests {
		tt := tt

		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			cache := NewRegexCache()

			compiled, _, err := cache.GetOrCompile(tt.expression)
			if err != nil {
				t.Fatalf("expected no error, got %v", err)
			}

			if got := compiled.MatchString(tt.value); got != tt.want {
				t.Fatalf("MatchString() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestRegexCache_GetOrCompile(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name        string
		expression  runtime.ExpressionRegex
		value       string
		wantMatch   bool
		wantErr     bool
		wantCached  bool
		wantEntries int
	}{
		{
			name: "compile matching regex",
			expression: runtime.ExpressionRegex{
				Expression: `^ghcr\.io/projectcapsule/.*`,
			},
			value:       "ghcr.io/projectcapsule/capsule:latest",
			wantMatch:   true,
			wantErr:     false,
			wantCached:  false,
			wantEntries: 1,
		},
		{
			name: "compile non matching regex",
			expression: runtime.ExpressionRegex{
				Expression: `^ghcr\.io/projectcapsule/.*`,
			},
			value:       "docker.io/library/nginx:latest",
			wantMatch:   false,
			wantErr:     false,
			wantCached:  false,
			wantEntries: 1,
		},
		{
			name: "compile negated matching regex",
			expression: runtime.ExpressionRegex{
				Expression: `^ghcr\.io/projectcapsule/.*`,
				Negate:     true,
			},
			value:       "ghcr.io/projectcapsule/capsule:latest",
			wantMatch:   false,
			wantErr:     false,
			wantCached:  false,
			wantEntries: 1,
		},
		{
			name: "compile negated non matching regex",
			expression: runtime.ExpressionRegex{
				Expression: `^ghcr\.io/projectcapsule/.*`,
				Negate:     true,
			},
			value:       "docker.io/library/nginx:latest",
			wantMatch:   true,
			wantErr:     false,
			wantCached:  false,
			wantEntries: 1,
		},
		{
			name: "reject empty expression",
			expression: runtime.ExpressionRegex{
				Expression: "",
			},
			value:       "ghcr.io/projectcapsule/capsule:latest",
			wantErr:     true,
			wantEntries: 0,
		},
		{
			name: "reject invalid regex",
			expression: runtime.ExpressionRegex{
				Expression: `[`,
			},
			value:       "ghcr.io/projectcapsule/capsule:latest",
			wantErr:     true,
			wantEntries: 0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			c := NewRegexCache()

			compiled, fromCache, err := c.GetOrCompile(tt.expression)
			if tt.wantErr {
				if err == nil {
					t.Fatal("expected error, got nil")
				}

				if compiled != nil {
					t.Fatalf("expected nil compiled regex on error, got %#v", compiled)
				}

				if got := c.Stats(); got != tt.wantEntries {
					t.Fatalf("expected %d cache entries, got %d", tt.wantEntries, got)
				}

				return
			}

			if err != nil {
				t.Fatalf("expected no error, got %v", err)
			}

			if compiled == nil {
				t.Fatal("expected compiled regex, got nil")
			}

			if fromCache != tt.wantCached {
				t.Fatalf("expected fromCache=%t, got %t", tt.wantCached, fromCache)
			}

			if got := compiled.MatchString(tt.value); got != tt.wantMatch {
				t.Fatalf("expected match=%t, got %t", tt.wantMatch, got)
			}

			if got := c.Stats(); got != tt.wantEntries {
				t.Fatalf("expected %d cache entries, got %d", tt.wantEntries, got)
			}

			if !c.Has(compiled.ID) {
				t.Fatalf("expected cache to contain regex id %q", compiled.ID)
			}
		})
	}
}

func TestRegexCache_GetOrCompile_ReusesCachedRegex(t *testing.T) {
	t.Parallel()

	c := NewRegexCache()

	expr := runtime.ExpressionRegex{
		Expression: `^ghcr\.io/projectcapsule/.*`,
	}

	first, fromCache, err := c.GetOrCompile(expr)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}

	if fromCache {
		t.Fatal("expected first lookup to build regex, got cache hit")
	}

	second, fromCache, err := c.GetOrCompile(expr)
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}

	if !fromCache {
		t.Fatal("expected second lookup to hit cache")
	}

	if first != second {
		t.Fatal("expected cached regex pointer to be reused")
	}

	if got := c.Stats(); got != 1 {
		t.Fatalf("expected 1 cache entry, got %d", got)
	}
}

func TestRegexCache_HashRegex_UsesNegate(t *testing.T) {
	t.Parallel()

	positive := HashRegex(runtime.ExpressionRegex{
		Expression: `^ghcr\.io/.*`,
	})

	negative := HashRegex(runtime.ExpressionRegex{
		Expression: `^ghcr\.io/.*`,
		Negate:     true,
	})

	if positive == negative {
		t.Fatal("expected different hashes for negated and non-negated expressions")
	}
}

func TestRegexCache_Reset(t *testing.T) {
	t.Parallel()

	c := NewRegexCache()

	compiled, _, err := c.GetOrCompile(runtime.ExpressionRegex{
		Expression: `^ghcr\.io/.*`,
	})
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}

	if got := c.Stats(); got != 1 {
		t.Fatalf("expected 1 cache entry, got %d", got)
	}

	c.Reset()

	if got := c.Stats(); got != 0 {
		t.Fatalf("expected 0 cache entries after reset, got %d", got)
	}

	if c.Has(compiled.ID) {
		t.Fatalf("expected regex id %q to be removed after reset", compiled.ID)
	}
}

func TestCompiledRegex_MatchString_NilSafe(t *testing.T) {
	t.Parallel()

	var compiled *CompiledRegex

	if compiled.MatchString("ghcr.io/projectcapsule/capsule:latest") {
		t.Fatal("expected nil compiled regex to return false")
	}

	compiled = &CompiledRegex{}

	if compiled.MatchString("ghcr.io/projectcapsule/capsule:latest") {
		t.Fatal("expected compiled regex with nil RE to return false")
	}
}
