package kascfg

import (
	"testing"

	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/testhelpers"
	"gitlab.com/gitlab-org/cluster-integration/gitlab-agent/v18/internal/tool/testing/testproto"
	"google.golang.org/protobuf/types/known/durationpb"
)

func TestValidation_Valid(t *testing.T) {
	tests := []testhelpers.ValidTestcase{
		{
			Name: "minimal",
			Valid: &ConfigurationFile{
				Gitlab: &GitLabCF{
					Address:                  "http://localhost:8080",
					AuthenticationSecretFile: "/some/file",
				},
				Redis: &RedisCF{
					RedisConfig: &RedisCF_Sentinel{
						Sentinel: &RedisSentinelCF{
							MasterName: "redis",
							Addresses:  []string{"redis:6379"},
						},
					},
				},
				Api: &ApiCF{
					Listen: &ListenApiCF{
						AuthenticationSecretFile: "/some/file",
					},
				},
				PrivateApi: &PrivateApiCF{
					Listen: &ListenPrivateApiCF{
						AuthenticationSecretFile: "/some/file",
					},
				},
			},
		},
		{
			Name: "AgentCF",
			Valid: &AgentCF{
				InfoCacheTtl:         durationpb.New(0), // zero means "disabled"
				RedisConnInfoRefresh: durationpb.New(1),
				RedisConnInfoTtl:     durationpb.New(2),
			},
		},
		{
			Name: "ObservabilityCF",
			Valid: &ObservabilityCF{
				UsageReportingPeriod: durationpb.New(0), // zero means "disabled"
			},
		},
		{
			Name: "TokenBucketRateLimitCF",
			Valid: &TokenBucketRateLimitCF{
				RefillRatePerSecond: 0, // zero means "use default value"
				BucketSize:          0, // zero means "use default value"
			},
		},
		{
			Name: "RedisCF",
			Valid: &RedisCF{
				RedisConfig: &RedisCF_Server{
					Server: &RedisServerCF{
						Address: "//path/to/socket.sock",
					},
				},
				PoolSize:  0,  // zero means "use default value"
				KeyPrefix: "", // empty means "use default value"
			},
		},
		{
			Name: "RedisCF",
			Valid: &RedisCF{
				RedisConfig: &RedisCF_Server{
					Server: &RedisServerCF{
						Address: "address:6380",
					},
				},
				PoolSize:  0,  // zero means "use default value"
				KeyPrefix: "", // empty means "use default value"
			},
		},
		{
			Name: "RedisCF",
			Valid: &RedisCF{
				RedisConfig: &RedisCF_Server{
					Server: &RedisServerCF{
						Address: "127.0.0.1:6380",
					},
				},
				PoolSize:  0,  // zero means "use default value"
				KeyPrefix: "", // empty means "use default value"
			},
		},
		{
			Name: "AgentConfigurationCF",
			Valid: &AgentConfigurationCF{
				MaxConfigurationFileSize: 0, // zero means "use default value"
			},
		},
		{
			Name: "ListenAgentCF",
			Valid: &ListenAgentCF{
				ConnectionsPerTokenPerMinute: 0, // zero means "use default value"
			},
		},
	}
	testhelpers.AssertValid(t, tests)
}

func TestValidation_Invalid(t *testing.T) {
	tests := []testhelpers.InvalidTestcase{
		{
			ErrString: "validation error: info_cache_ttl: value must be greater than or equal to 0s",
			Invalid: &AgentCF{
				InfoCacheTtl:         durationpb.New(-1),
				RedisConnInfoRefresh: durationpb.New(1),
				RedisConnInfoTtl:     durationpb.New(2),
			},
		},
		{
			ErrString: "validation error: info_cache_error_ttl: value must be greater than 0s",
			Invalid: &AgentCF{
				InfoCacheErrorTtl:    durationpb.New(0),
				RedisConnInfoRefresh: durationpb.New(1),
				RedisConnInfoTtl:     durationpb.New(2),
			},
		},
		{
			ErrString: "validation error: info_cache_error_ttl: value must be greater than 0s",
			Invalid: &AgentCF{
				InfoCacheErrorTtl:    durationpb.New(-1),
				RedisConnInfoRefresh: durationpb.New(1),
				RedisConnInfoTtl:     durationpb.New(2),
			},
		},
		{
			ErrString: "validation error: redis_conn_info_refresh should be < redis_conn_info_ttl",
			Invalid: &AgentCF{
				RedisConnInfoRefresh: durationpb.New(1),
				RedisConnInfoTtl:     durationpb.New(1),
			},
		},
		{
			ErrString: "validation error: redis_conn_info_refresh should be < redis_conn_info_ttl",
			Invalid: &AgentCF{
				RedisConnInfoRefresh: durationpb.New(2),
				RedisConnInfoTtl:     durationpb.New(1),
			},
		},
		{
			ErrString: "validation error: poll_period: value must be greater than 0s",
			Invalid: &AgentConfigurationCF{
				PollPeriod: durationpb.New(0),
			},
		},
		{
			ErrString: "validation error: poll_period: value must be greater than 0s",
			Invalid: &AgentConfigurationCF{
				PollPeriod: durationpb.New(-1),
			},
		},
		{
			ErrString: "validation error: usage_reporting_period: value must be greater than or equal to 0s",
			Invalid: &ObservabilityCF{
				UsageReportingPeriod: durationpb.New(-1),
			},
		},
		{
			ErrString: "validation error: refill_rate_per_second: value must be greater than or equal to 0",
			Invalid: &TokenBucketRateLimitCF{
				RefillRatePerSecond: -1,
			},
		},
		{
			ErrString: "validation error: dial_timeout: value must be greater than 0s",
			Invalid: &RedisCF{
				RedisConfig: &RedisCF_Server{
					Server: &RedisServerCF{
						Address: "//path/to/socket.sock",
					},
				},
				DialTimeout: durationpb.New(0),
			},
		},
		{
			ErrString: "validation error: dial_timeout: value must be greater than 0s",
			Invalid: &RedisCF{
				RedisConfig: &RedisCF_Server{
					Server: &RedisServerCF{
						Address: "//path/to/socket.sock",
					},
				},
				DialTimeout: durationpb.New(-1),
			},
		},
		{
			ErrString: "validation error: read_timeout: value must be greater than 0s",
			Invalid: &RedisCF{
				RedisConfig: &RedisCF_Server{
					Server: &RedisServerCF{
						Address: "//path/to/socket.sock",
					},
				},
				ReadTimeout: durationpb.New(0),
			},
		},
		{
			ErrString: "validation error: read_timeout: value must be greater than 0s",
			Invalid: &RedisCF{
				RedisConfig: &RedisCF_Server{
					Server: &RedisServerCF{
						Address: "//path/to/socket.sock",
					},
				},
				ReadTimeout: durationpb.New(-1),
			},
		},
		{
			ErrString: "validation error: write_timeout: value must be greater than 0s",
			Invalid: &RedisCF{
				RedisConfig: &RedisCF_Server{
					Server: &RedisServerCF{
						Address: "//path/to/socket.sock",
					},
				},
				WriteTimeout: durationpb.New(0),
			},
		},
		{
			ErrString: "validation error: write_timeout: value must be greater than 0s",
			Invalid: &RedisCF{
				RedisConfig: &RedisCF_Server{
					Server: &RedisServerCF{
						Address: "//path/to/socket.sock",
					},
				},
				WriteTimeout: durationpb.New(-1),
			},
		},
		{
			ErrString: "validation error: idle_timeout: value must be greater than 0s",
			Invalid: &RedisCF{
				RedisConfig: &RedisCF_Server{
					Server: &RedisServerCF{
						Address: "//path/to/socket.sock",
					},
				},
				IdleTimeout: durationpb.New(0),
			},
		},
		{
			ErrString: "validation error: idle_timeout: value must be greater than 0s",
			Invalid: &RedisCF{
				RedisConfig: &RedisCF_Server{
					Server: &RedisServerCF{
						Address: "//path/to/socket.sock",
					},
				},
				IdleTimeout: durationpb.New(-1),
			},
		},
		{
			ErrString: "validation error: redis_config: exactly one field is required in oneof",
			Invalid:   &RedisCF{},
		},
		{
			ErrString: "validation error: server.address: value length must be at least 1 bytes",
			Invalid: &RedisCF{
				RedisConfig: &RedisCF_Server{},
			},
		},
		{
			ErrString: "validation errors:\n - sentinel.master_name: value length must be at least 1 bytes\n - sentinel.addresses: value must contain at least 1 item(s)",
			Invalid: &RedisCF{
				RedisConfig: &RedisCF_Sentinel{},
			},
		},
		{
			ErrString: "validation error: address: value length must be at least 1 bytes",
			Invalid:   &RedisServerCF{},
		},
		{
			ErrString: "validation error: master_name: value length must be at least 1 bytes",
			Invalid: &RedisSentinelCF{
				Addresses: []string{"1:2"},
			},
		},
		{
			ErrString: "validation error: addresses: value must contain at least 1 item(s)",
			Invalid: &RedisSentinelCF{
				MasterName: "bla",
			},
		},
		{
			ErrString: "validation error: addresses[0]: value length must be at least 1 bytes",
			Invalid: &RedisSentinelCF{
				MasterName: "bla",
				Addresses:  []string{""},
			},
		},
		{
			ErrString: "validation error: max_connection_age: value must be greater than 0s",
			Invalid: &ListenAgentCF{
				MaxConnectionAge: durationpb.New(0),
			},
		},
		{
			ErrString: "validation error: max_connection_age: value must be greater than 0s",
			Invalid: &ListenAgentCF{
				MaxConnectionAge: durationpb.New(-1),
			},
		},
		{
			ErrString: "validation errors:\n - address: value length must be at least 1 bytes\n - address: value is empty, which is not a valid URI\n - authentication_secret_file: value length must be at least 1 bytes",
			Invalid:   &GitLabCF{},
		},
		{
			ErrString: "validation errors:\n - gitlab: value is required\n - redis: value is required\n - api: value is required\n - private_api: value is required",
			Invalid:   &ConfigurationFile{},
		},
		{
			ErrString: "validation error: authentication_secret_file: value length must be at least 1 bytes",
			Invalid:   &ListenApiCF{},
		},
		{
			ErrString: "validation error: max_connection_age: value must be greater than 0s",
			Invalid: &ListenApiCF{
				AuthenticationSecretFile: "bla",
				MaxConnectionAge:         durationpb.New(0),
			},
		},
		{
			ErrString: "validation error: max_connection_age: value must be greater than 0s",
			Invalid: &ListenApiCF{
				AuthenticationSecretFile: "bla",
				MaxConnectionAge:         durationpb.New(-1),
			},
		},
		{
			ErrString: "validation error: listen: value is required",
			Invalid:   &ApiCF{},
		},
		{
			ErrString: "validation error: listen: value is required",
			Invalid:   &PrivateApiCF{},
		},
	}
	testhelpers.AssertInvalid(t, tests)
}

func TestJSONNameSet(t *testing.T) {
	cfg := ConfigurationFile{}
	testproto.AssertHasJSONNameSetRecursive(t, cfg.ProtoReflect().Descriptor())
}
