// Copyright 2024 Woodpecker Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package kubernetes

import (
	"encoding/json"
	"testing"

	"github.com/kinbiko/jsonassert"
	"github.com/stretchr/testify/assert"
	kube_core_v1 "k8s.io/api/core/v1"

	"go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types"
)

func TestNativeSecretsEnabled(t *testing.T) {
	nsp := newNativeSecretsProcessor(&config{
		NativeSecretsAllowFromStep: true,
	}, nil)
	assert.True(t, nsp.isEnabled())
}

func TestNativeSecretsDisabled(t *testing.T) {
	nsp := newNativeSecretsProcessor(&config{
		NativeSecretsAllowFromStep: false,
	}, []SecretRef{
		{
			Name: "env-simple",
		},
		{
			Name: "env-advanced",
			Key:  "key",
			Target: SecretTarget{
				Env: "ENV_VAR",
			},
		},
		{
			Name: "env-file",
			Key:  "cert",
			Target: SecretTarget{
				File: "/etc/ca/x3.cert",
			},
		},
	})
	assert.False(t, nsp.isEnabled())

	err := nsp.process()
	assert.NoError(t, err)
	assert.Empty(t, nsp.envFromSources)
	assert.Empty(t, nsp.envVars)
	assert.Empty(t, nsp.volumes)
	assert.Empty(t, nsp.mounts)
}

func TestSimpleSecret(t *testing.T) {
	nsp := newNativeSecretsProcessor(&config{
		NativeSecretsAllowFromStep: true,
	}, []SecretRef{
		{
			Name: "test-secret",
		},
	})

	err := nsp.process()
	assert.NoError(t, err)
	assert.Empty(t, nsp.envVars)
	assert.Empty(t, nsp.volumes)
	assert.Empty(t, nsp.mounts)
	assert.Equal(t, []kube_core_v1.EnvFromSource{
		{
			SecretRef: &kube_core_v1.SecretEnvSource{
				LocalObjectReference: kube_core_v1.LocalObjectReference{Name: "test-secret"},
			},
		},
	}, nsp.envFromSources)
}

func TestSecretWithKey(t *testing.T) {
	nsp := newNativeSecretsProcessor(&config{
		NativeSecretsAllowFromStep: true,
	}, []SecretRef{
		{
			Name: "test-secret",
			Key:  "access_key",
		},
	})

	err := nsp.process()
	assert.NoError(t, err)
	assert.Empty(t, nsp.envFromSources)
	assert.Empty(t, nsp.volumes)
	assert.Empty(t, nsp.mounts)
	assert.Equal(t, []kube_core_v1.EnvVar{
		{
			Name: "ACCESS_KEY",
			ValueFrom: &kube_core_v1.EnvVarSource{
				SecretKeyRef: &kube_core_v1.SecretKeySelector{
					LocalObjectReference: kube_core_v1.LocalObjectReference{Name: "test-secret"},
					Key:                  "access_key",
				},
			},
		},
	}, nsp.envVars)
}

func TestSecretWithKeyMapping(t *testing.T) {
	nsp := newNativeSecretsProcessor(&config{
		NativeSecretsAllowFromStep: true,
	}, []SecretRef{
		{
			Name: "test-secret",
			Key:  "aws-secret",
			Target: SecretTarget{
				Env: "AWS_SECRET_ACCESS_KEY",
			},
		},
	})

	err := nsp.process()
	assert.NoError(t, err)
	assert.Empty(t, nsp.envFromSources)
	assert.Empty(t, nsp.volumes)
	assert.Empty(t, nsp.mounts)
	assert.Equal(t, []kube_core_v1.EnvVar{
		{
			Name: "AWS_SECRET_ACCESS_KEY",
			ValueFrom: &kube_core_v1.EnvVarSource{
				SecretKeyRef: &kube_core_v1.SecretKeySelector{
					LocalObjectReference: kube_core_v1.LocalObjectReference{Name: "test-secret"},
					Key:                  "aws-secret",
				},
			},
		},
	}, nsp.envVars)
}

func TestFileSecret(t *testing.T) {
	nsp := newNativeSecretsProcessor(&config{
		NativeSecretsAllowFromStep: true,
	}, []SecretRef{
		{
			Name: "reg-cred",
			Key:  ".dockerconfigjson",
			Target: SecretTarget{
				File: "~/.docker/config.json",
			},
		},
	})

	err := nsp.process()
	assert.NoError(t, err)
	assert.Empty(t, nsp.envFromSources)
	assert.Empty(t, nsp.envVars)
	assert.Equal(t, []kube_core_v1.Volume{
		{
			Name: "reg-cred",
			VolumeSource: kube_core_v1.VolumeSource{
				Secret: &kube_core_v1.SecretVolumeSource{
					SecretName: "reg-cred",
				},
			},
		},
	}, nsp.volumes)
	assert.Equal(t, []kube_core_v1.VolumeMount{
		{
			Name:      "reg-cred",
			ReadOnly:  true,
			MountPath: "~/.docker/config.json",
			SubPath:   ".dockerconfigjson",
		},
	}, nsp.mounts)
}

func TestNoAuthNoSecret(t *testing.T) {
	assert.False(t, needsRegistrySecret(&types.Step{}))
}

func TestNoPasswordNoSecret(t *testing.T) {
	assert.False(t, needsRegistrySecret(&types.Step{
		AuthConfig: types.Auth{Username: "foo"},
	}))
}

func TestNoUsernameNoSecret(t *testing.T) {
	assert.False(t, needsRegistrySecret(&types.Step{
		AuthConfig: types.Auth{Password: "foo"},
	}))
}

func TestUsernameAndPasswordNeedsSecret(t *testing.T) {
	assert.True(t, needsRegistrySecret(&types.Step{
		AuthConfig: types.Auth{Username: "foo", Password: "bar"},
	}))
}

func TestRegistrySecret(t *testing.T) {
	const expected = `{
		"metadata": {
			"name": "wp-01he8bebctabr3kgk0qj36d2me-0",
			"namespace": "woodpecker",
			"labels": {
				"step": "go-test",
				"woodpecker-ci.org/step": "go-test"
			}
		},
		"type": "kubernetes.io/dockerconfigjson",
		"data": {
			".dockerconfigjson": "eyJhdXRocyI6eyJkb2NrZXIuaW8iOnsidXNlcm5hbWUiOiJmb28iLCJwYXNzd29yZCI6ImJhciJ9fX0="
		}
	}`

	secret, err := mkRegistrySecret(&types.Step{
		UUID:  "01he8bebctabr3kgk0qj36d2me-0",
		Name:  "go-test",
		Image: "meltwater/drone-cache",
		AuthConfig: types.Auth{
			Username: "foo",
			Password: "bar",
		},
	}, &config{
		Namespace: "woodpecker",
	})
	assert.NoError(t, err)

	secretJSON, err := json.Marshal(secret)
	assert.NoError(t, err)

	ja := jsonassert.New(t)
	ja.Assertf(string(secretJSON), expected)
}
