// Copyright 2023 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 compiler

import (
	"testing"

	"github.com/stretchr/testify/assert"

	backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types"
	"go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata"
	yaml_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types"
	yaml_base_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types/base"
	"go.woodpecker-ci.org/woodpecker/v2/shared/constant"
)

func TestSecretAvailable(t *testing.T) {
	secret := Secret{
		AllowedPlugins: []string{},
	}
	assert.True(t, secret.Available(&yaml_types.Container{
		Image:    "golang",
		Commands: yaml_base_types.StringOrSlice{"echo 'this is not a plugin'"},
	}))

	// secret only available for "golang" plugin
	secret = Secret{
		AllowedPlugins: []string{"golang"},
	}
	assert.True(t, secret.Available(&yaml_types.Container{
		Image:    "golang",
		Commands: yaml_base_types.StringOrSlice{},
	}))
	assert.False(t, secret.Available(&yaml_types.Container{
		Image:    "golang",
		Commands: yaml_base_types.StringOrSlice{"echo 'this is not a plugin'"},
	}))
	assert.False(t, secret.Available(&yaml_types.Container{
		Image:    "not-golang",
		Commands: yaml_base_types.StringOrSlice{},
	}))
}

func TestCompilerCompile(t *testing.T) {
	compiler := New(
		WithMetadata(metadata.Metadata{
			Repo: metadata.Repo{
				Owner:    "octacat",
				Name:     "hello-world",
				Private:  true,
				ForgeURL: "https://github.com/octocat/hello-world",
				CloneURL: "https://github.com/octocat/hello-world.git",
			},
		}),
		WithEnviron(map[string]string{
			"VERBOSE": "true",
			"COLORED": "true",
		}),
		WithPrefix("test"),
	)

	defaultNetworks := []*backend_types.Network{{
		Name: "test_default",
	}}
	defaultVolumes := []*backend_types.Volume{{
		Name: "test_default",
	}}

	defaultCloneStage := &backend_types.Stage{
		Steps: []*backend_types.Step{{
			Name:       "clone",
			Type:       backend_types.StepTypeClone,
			Image:      constant.DefaultCloneImage,
			OnSuccess:  true,
			Failure:    "fail",
			Volumes:    []string{defaultVolumes[0].Name + ":"},
			Networks:   []backend_types.Conn{{Name: "test_default", Aliases: []string{"clone"}}},
			ExtraHosts: []backend_types.HostAlias{},
		}},
	}

	tests := []struct {
		name        string
		fronConf    *yaml_types.Workflow
		backConf    *backend_types.Config
		expectedErr string
	}{
		{
			name:     "empty workflow, no clone",
			fronConf: &yaml_types.Workflow{SkipClone: true},
			backConf: &backend_types.Config{
				Networks: defaultNetworks,
				Volumes:  defaultVolumes,
			},
		},
		{
			name:     "empty workflow, default clone",
			fronConf: &yaml_types.Workflow{},
			backConf: &backend_types.Config{
				Networks: defaultNetworks,
				Volumes:  defaultVolumes,
				Stages:   []*backend_types.Stage{defaultCloneStage},
			},
		},
		{
			name: "workflow with one dummy step",
			fronConf: &yaml_types.Workflow{Steps: yaml_types.ContainerList{ContainerList: []*yaml_types.Container{{
				Name:  "dummy",
				Image: "dummy_img",
			}}}},
			backConf: &backend_types.Config{
				Networks: defaultNetworks,
				Volumes:  defaultVolumes,
				Stages: []*backend_types.Stage{defaultCloneStage, {
					Steps: []*backend_types.Step{{
						Name:       "dummy",
						Type:       backend_types.StepTypePlugin,
						Image:      "dummy_img",
						OnSuccess:  true,
						Failure:    "fail",
						Volumes:    []string{defaultVolumes[0].Name + ":"},
						Networks:   []backend_types.Conn{{Name: "test_default", Aliases: []string{"dummy"}}},
						ExtraHosts: []backend_types.HostAlias{},
					}},
				}},
			},
		},
		{
			name: "workflow with three steps and one group",
			fronConf: &yaml_types.Workflow{Steps: yaml_types.ContainerList{ContainerList: []*yaml_types.Container{{
				Name:     "echo env",
				Image:    "bash",
				Commands: []string{"env"},
			}, {
				Name:     "parallel echo 1",
				Group:    "parallel",
				Image:    "bash",
				Commands: []string{"echo 1"},
			}, {
				Name:     "parallel echo 2",
				Group:    "parallel",
				Image:    "bash",
				Commands: []string{"echo 2"},
			}}}},
			backConf: &backend_types.Config{
				Networks: defaultNetworks,
				Volumes:  defaultVolumes,
				Stages: []*backend_types.Stage{defaultCloneStage, {
					Steps: []*backend_types.Step{{
						Name:       "echo env",
						Type:       backend_types.StepTypeCommands,
						Image:      "bash",
						Commands:   []string{"env"},
						OnSuccess:  true,
						Failure:    "fail",
						Volumes:    []string{defaultVolumes[0].Name + ":"},
						Networks:   []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}},
						ExtraHosts: []backend_types.HostAlias{},
					}},
				}, {
					Steps: []*backend_types.Step{{
						Name:       "parallel echo 1",
						Type:       backend_types.StepTypeCommands,
						Image:      "bash",
						Commands:   []string{"echo 1"},
						OnSuccess:  true,
						Failure:    "fail",
						Volumes:    []string{defaultVolumes[0].Name + ":"},
						Networks:   []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 1"}}},
						ExtraHosts: []backend_types.HostAlias{},
					}, {
						Name:       "parallel echo 2",
						Type:       backend_types.StepTypeCommands,
						Image:      "bash",
						Commands:   []string{"echo 2"},
						OnSuccess:  true,
						Failure:    "fail",
						Volumes:    []string{defaultVolumes[0].Name + ":"},
						Networks:   []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 2"}}},
						ExtraHosts: []backend_types.HostAlias{},
					}},
				}},
			},
		},
		{
			name: "workflow with three steps and depends_on",
			fronConf: &yaml_types.Workflow{Steps: yaml_types.ContainerList{ContainerList: []*yaml_types.Container{{
				Name:     "echo env",
				Image:    "bash",
				Commands: []string{"env"},
			}, {
				Name:      "echo 1",
				Image:     "bash",
				Commands:  []string{"echo 1"},
				DependsOn: []string{"echo env", "echo 2"},
			}, {
				Name:     "echo 2",
				Image:    "bash",
				Commands: []string{"echo 2"},
			}}}},
			backConf: &backend_types.Config{
				Networks: defaultNetworks,
				Volumes:  defaultVolumes,
				Stages: []*backend_types.Stage{defaultCloneStage, {
					Steps: []*backend_types.Step{{
						Name:       "echo env",
						Type:       backend_types.StepTypeCommands,
						Image:      "bash",
						Commands:   []string{"env"},
						OnSuccess:  true,
						Failure:    "fail",
						Volumes:    []string{defaultVolumes[0].Name + ":"},
						Networks:   []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}},
						ExtraHosts: []backend_types.HostAlias{},
					}, {
						Name:       "echo 2",
						Type:       backend_types.StepTypeCommands,
						Image:      "bash",
						Commands:   []string{"echo 2"},
						OnSuccess:  true,
						Failure:    "fail",
						Volumes:    []string{defaultVolumes[0].Name + ":"},
						Networks:   []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo 2"}}},
						ExtraHosts: []backend_types.HostAlias{},
					}},
				}, {
					Steps: []*backend_types.Step{{
						Name:       "echo 1",
						Type:       backend_types.StepTypeCommands,
						Image:      "bash",
						Commands:   []string{"echo 1"},
						OnSuccess:  true,
						Failure:    "fail",
						Volumes:    []string{defaultVolumes[0].Name + ":"},
						Networks:   []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo 1"}}},
						ExtraHosts: []backend_types.HostAlias{},
					}},
				}},
			},
		},
		{
			name: "workflow with missing secret",
			fronConf: &yaml_types.Workflow{Steps: yaml_types.ContainerList{ContainerList: []*yaml_types.Container{{
				Name:     "step",
				Image:    "bash",
				Commands: []string{"env"},
				Secrets:  yaml_types.Secrets{Secrets: []*yaml_types.Secret{{Source: "missing", Target: "missing"}}},
			}}}},
			backConf:    nil,
			expectedErr: "secret \"missing\" not found or not allowed to be used",
		},
		{
			name: "workflow with broken step dependency",
			fronConf: &yaml_types.Workflow{Steps: yaml_types.ContainerList{ContainerList: []*yaml_types.Container{{
				Name:      "dummy",
				Image:     "dummy_img",
				DependsOn: []string{"not exist"},
			}}}},
			backConf:    nil,
			expectedErr: "step 'dummy' depends on unknown step 'not exist'",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			backConf, err := compiler.Compile(test.fronConf)
			if test.expectedErr != "" {
				assert.Error(t, err)
				assert.Equal(t, err.Error(), test.expectedErr)
			} else {
				// we ignore uuids in steps and only check if global env got set ...
				for _, st := range backConf.Stages {
					for _, s := range st.Steps {
						s.UUID = ""
						assert.Truef(t, s.Environment["VERBOSE"] == "true", "expect to get value of global set environment")
						assert.Truef(t, len(s.Environment) > 50, "expect to have a lot of build in variables")
						s.Environment = nil
					}
				}
				// check if we get an expected backend config based on a frontend config
				assert.EqualValues(t, *test.backConf, *backConf)
			}
		})
	}
}
