//  Copyright 2021 Google Inc. All Rights Reserved.
//
//  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 config

import (
	"context"
	"io/ioutil"
	"math/rand"
	"os"
	"path"
	"path/filepath"
	"reflect"
	"testing"

	"cloud.google.com/go/osconfig/agentendpoint/apiv1/agentendpointpb"
)

func TestExecResourceDownload(t *testing.T) {
	ctx := context.Background()

	var tests = []struct {
		name                 string
		erpb                 *agentendpointpb.OSPolicy_Resource_ExecResource
		wantValidatePath     string
		wantValidateContents string
		wantEnforcePath      string
		wantEnforceContents  string
		goos                 string
	}{
		{
			"Script NONE Linux",
			&agentendpointpb.OSPolicy_Resource_ExecResource{
				Validate: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "validate"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_NONE,
				},
				Enforce: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "enforce"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_NONE,
				},
			},
			"script",
			"validate",
			"script",
			"enforce",
			"linux",
		},
		{
			"Script NONE Windows",
			&agentendpointpb.OSPolicy_Resource_ExecResource{
				Validate: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "validate"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_NONE,
				},
				Enforce: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "enforce"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_NONE,
				},
			},
			"script.cmd",
			"validate",
			"script.cmd",
			"enforce",
			"windows",
		},
		{
			"Script SHELL Linux",
			&agentendpointpb.OSPolicy_Resource_ExecResource{
				Validate: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "validate"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_SHELL,
				},
				Enforce: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "enforce"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_SHELL,
				},
			},
			"script.sh",
			"validate",
			"script.sh",
			"enforce",
			"linux",
		},
		{
			"Script SHELL Windows",
			&agentendpointpb.OSPolicy_Resource_ExecResource{
				Validate: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "validate"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_SHELL,
				},
				Enforce: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "enforce"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_SHELL,
				},
			},
			"script.cmd",
			"validate",
			"script.cmd",
			"enforce",
			"windows",
		},
		{
			"Script POWERSHELL Windows",
			&agentendpointpb.OSPolicy_Resource_ExecResource{
				Validate: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "validate"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_POWERSHELL,
				},
				Enforce: &agentendpointpb.OSPolicy_Resource_ExecResource_Exec{
					Source:      &agentendpointpb.OSPolicy_Resource_ExecResource_Exec_Script{Script: "enforce"},
					Interpreter: agentendpointpb.OSPolicy_Resource_ExecResource_Exec_POWERSHELL,
				},
			},
			"script.ps1",
			"validate",
			"script.ps1",
			"enforce",
			"windows",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			goos = tt.goos
			pr := &OSPolicyResource{
				OSPolicy_Resource: &agentendpointpb.OSPolicy_Resource{
					ResourceType: &agentendpointpb.OSPolicy_Resource_Exec{
						Exec: tt.erpb,
					},
				},
			}
			defer pr.Cleanup(ctx)

			if err := pr.Validate(ctx); err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}
			resource := pr.resource.(*execResource)

			if tt.wantValidatePath != path.Base(resource.validatePath) {
				t.Errorf("unexpected validate path: %q", resource.validatePath)
			}
			data, err := ioutil.ReadFile(resource.validatePath)
			if err != nil {
				t.Fatal(err)
			}
			if tt.wantValidateContents != string(data) {
				t.Errorf("unexpected validate contents: %q", data)
			}

			if tt.wantEnforcePath != path.Base(resource.enforcePath) {
				t.Errorf("unexpected enforce path: %q", resource.enforcePath)
			}
			data, err = ioutil.ReadFile(resource.enforcePath)
			if err != nil {
				t.Fatal(err)
			}
			if tt.wantEnforceContents != string(data) {
				t.Errorf("unexpected enforce contents: %q", data)
			}
		})
	}
}

func TestExecOutput(t *testing.T) {
	ctx := context.Background()
	tmpDir, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir)

	fileA := filepath.Join(tmpDir, "fileA")
	contentsA := []byte("here is some text\nand some more\n")
	if err := ioutil.WriteFile(fileA, contentsA, 0600); err != nil {
		t.Fatal(err)
	}

	fileB := filepath.Join(tmpDir, "fileB")
	contentsB := make([]byte, maxExecOutputSize*2)
	if _, err := rand.Read(contentsB); err != nil {
		t.Fatal(err)
	}
	if err := ioutil.WriteFile(fileB, contentsB, 0600); err != nil {
		t.Fatal(err)
	}

	var tests = []struct {
		name     string
		filePath string
		want     []byte
		wantErr  bool
	}{
		{
			"empty path",
			"",
			nil,
			false,
		},
		{
			"path DNE",
			"DNE",
			nil,
			true,
		},
		{
			"normal case",
			fileA,
			contentsA,
			false,
		},
		{
			"file to large case",
			fileB,
			contentsB[:maxExecOutputSize],
			true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := execOutput(ctx, tt.filePath)
			if err != nil && !tt.wantErr {
				t.Errorf("Unexpected error from execOutput: %v", err)
			}
			if err == nil && tt.wantErr {
				t.Error("Did not get expected error from execOutput")
			}

			if !reflect.DeepEqual(got, tt.want) {
				t.Errorf("got != want, string(got) = %q string(want) = %q", got, tt.want)
			}
		})
	}
}
