// Copyright 2026 The gVisor 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 cmd

import (
	"os"
	"testing"

	specs "github.com/opencontainers/runtime-spec/specs-go"
	"gvisor.dev/gvisor/runsc/config"
	sandboxexecpb "gvisor.dev/gvisor/sandboxexec/proto/sandbox_options_go_proto"
)

func TestCreateSpecDefault(t *testing.T) {
	opts := &sandboxexecpb.SandboxOptions{}
	conf := &config.Config{}
	args := []string{"echo", "hello"}

	spec, err := createSpec(opts, args, conf)
	if err != nil {
		t.Fatalf("createSpec failed: %v", err)
	}

	if spec.Root == nil {
		t.Fatal("spec.Root is nil")
	}
	if spec.Root.Path != "rootfs" {
		t.Errorf("expected Root.Path to be 'rootfs', got %q", spec.Root.Path)
	}

	if len(spec.Process.Args) != 2 || spec.Process.Args[0] != "echo" || spec.Process.Args[1] != "hello" {
		t.Errorf("expected args ['echo', 'hello'], got %v", spec.Process.Args)
	}

	// Check default mounts are present
	expectedMounts := []string{"/proc", "/sys", "/dev", "/dev/pts", "/sys/fs/cgroup", "/tmp"}
	for _, want := range expectedMounts {
		found := false
		for _, m := range spec.Mounts {
			if m.Destination == want {
				found = true
				break
			}
		}
		if !found {
			t.Errorf("default mount %s not found", want)
		}
	}
}

func TestCreateSpecEnvVars(t *testing.T) {
	opts := &sandboxexecpb.SandboxOptions{
		EnvVars: []*sandboxexecpb.EnvVar{
			{
				Name: "VAR1",
				PolicyOrValue: &sandboxexecpb.EnvVar_Value{
					Value: "val1",
				},
			},
			{
				Name: "VAR2",
				PolicyOrValue: &sandboxexecpb.EnvVar_Policy{
					Policy: sandboxexecpb.EnvVar_ENV_VAR_POLICY_FORWARD,
				},
			},
		},
	}
	os.Setenv("VAR2", "val2_forwarded")
	defer os.Unsetenv("VAR2")

	conf := &config.Config{}
	args := []string{"true"}

	spec, err := createSpec(opts, args, conf)
	if err != nil {
		t.Fatalf("createSpec failed: %v", err)
	}

	foundVar1 := false
	foundVar2 := false
	for _, env := range spec.Process.Env {
		if env == "VAR1=val1" {
			foundVar1 = true
		}
		if env == "VAR2=val2_forwarded" {
			foundVar2 = true
		}
	}
	if !foundVar1 {
		t.Error("VAR1=val1 not found in spec.Process.Env")
	}
	if !foundVar2 {
		t.Error("VAR2=val2_forwarded not found in spec.Process.Env")
	}
}

func TestCreateSpecMounts(t *testing.T) {
	opts := &sandboxexecpb.SandboxOptions{
		Mounts: []*sandboxexecpb.Mount{
			{
				Target: "/mnt/host",
				Mount: &sandboxexecpb.Mount_HostMount{
					HostMount: &sandboxexecpb.HostMount{
						HostPath: "/var/lib/data",
					},
				},
			},
			{
				Target: "/mnt/tmp",
				Mount: &sandboxexecpb.Mount_SandboxTmpfsMount{
					SandboxTmpfsMount: &sandboxexecpb.SandboxTmpfsMount{},
				},
			},
		},
	}

	conf := &config.Config{}
	args := []string{"true"}

	spec, err := createSpec(opts, args, conf)
	if err != nil {
		t.Fatalf("createSpec failed: %v", err)
	}

	foundHostMount := false
	foundTmpfsMount := false
	for _, m := range spec.Mounts {
		if m.Destination == "/mnt/host" && m.Type == "bind" && m.Source == "/var/lib/data" {
			foundHostMount = true
		}
		if m.Destination == "/mnt/tmp" && m.Type == "tmpfs" {
			foundTmpfsMount = true
		}
	}
	if !foundHostMount {
		t.Error("host mount not found or incorrect")
	}
	if !foundTmpfsMount {
		t.Error("tmpfs mount not found or incorrect")
	}
}

func TestCreateSpecNetworking(t *testing.T) {
	opts := &sandboxexecpb.SandboxOptions{
		NetworkingOptions: &sandboxexecpb.NetworkingOptions{
			EnableExternalNetworking: true,
		},
	}

	conf := &config.Config{}
	args := []string{"true"}

	_, err := createSpec(opts, args, conf)
	if err != nil {
		t.Fatalf("createSpec failed: %v", err)
	}

	if conf.Network != config.NetworkSandbox {
		t.Errorf("expected network to be %v, got %v", config.NetworkSandbox, conf.Network)
	}
}

func TestCreateSpecRootless(t *testing.T) {
	opts := &sandboxexecpb.SandboxOptions{}
	conf := &config.Config{Rootless: true}
	args := []string{"true"}

	spec, err := createSpec(opts, args, conf)
	if err != nil {
		t.Fatalf("createSpec failed: %v", err)
	}

	if spec.Linux == nil {
		t.Fatal("spec.Linux is nil, expected UserNamespace config")
	}

	foundUserNS := false
	for _, ns := range spec.Linux.Namespaces {
		if ns.Type == specs.UserNamespace {
			foundUserNS = true
			break
		}
	}
	if !foundUserNS {
		t.Error("UserNamespace not found in spec.Linux.Namespaces")
	}

	if len(spec.Linux.UIDMappings) == 0 {
		t.Error("UIDMappings empty")
	}
	if len(spec.Linux.GIDMappings) == 0 {
		t.Error("GIDMappings empty")
	}
}

func TestCreateSpecEnvExposure(t *testing.T) {
	secretName := "LAUNCHER_SECRET"
	secretVal := "super_secret_value"
	os.Setenv(secretName, secretVal)
	defer os.Unsetenv(secretName)

	conf := &config.Config{}
	args := []string{"true"}

	// 1. Default behavior: should NOT inherit host env vars.
	{
		opts := &sandboxexecpb.SandboxOptions{}
		spec, err := createSpec(opts, args, conf)
		if err != nil {
			t.Fatalf("createSpec failed: %v", err)
		}
		for _, env := range spec.Process.Env {
			if env == secretName+"="+secretVal || env == secretName {
				t.Errorf("default spec inherited secret env var: %s", env)
			}
		}
	}

	// 2. Forward policy: should inherit.
	{
		opts := &sandboxexecpb.SandboxOptions{
			EnvVars: []*sandboxexecpb.EnvVar{
				{
					Name: secretName,
					PolicyOrValue: &sandboxexecpb.EnvVar_Policy{
						Policy: sandboxexecpb.EnvVar_ENV_VAR_POLICY_FORWARD,
					},
				},
			},
		}
		spec, err := createSpec(opts, args, conf)
		if err != nil {
			t.Fatalf("createSpec failed: %v", err)
		}
		found := false
		for _, env := range spec.Process.Env {
			if env == secretName+"="+secretVal {
				found = true
				break
			}
		}
		if !found {
			t.Errorf("forwarded env var %s not found in spec", secretName)
		}
	}

	// 3. Unset policy: should NOT inherit, even if it was in host.
	{
		opts := &sandboxexecpb.SandboxOptions{
			EnvVars: []*sandboxexecpb.EnvVar{
				{
					Name: secretName,
					PolicyOrValue: &sandboxexecpb.EnvVar_Policy{
						Policy: sandboxexecpb.EnvVar_ENV_VAR_POLICY_UNSET,
					},
				},
			},
		}
		spec, err := createSpec(opts, args, conf)
		if err != nil {
			t.Fatalf("createSpec failed: %v", err)
		}
		for _, env := range spec.Process.Env {
			if env == secretName+"="+secretVal || env == secretName {
				t.Errorf("unset env var %s still found in spec: %s", secretName, env)
			}
		}
	}
}
