/**
# Copyright (c) 2021, NVIDIA CORPORATION.  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 tegra

import (
	"fmt"
	"testing"

	"github.com/NVIDIA/nvidia-container-toolkit/internal/discover"
	"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
	"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup"
	testlog "github.com/sirupsen/logrus/hooks/test"
	"github.com/stretchr/testify/require"

	"github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra/csv"
)

func TestDiscovererFromCSVFiles(t *testing.T) {
	logger, _ := testlog.NewNullLogger()
	testCases := []struct {
		description         string
		moutSpecs           map[csv.MountSpecType][]string
		ignorePatterns      []string
		symlinkLocator      lookup.Locator
		symlinkChainLocator lookup.Locator
		symlinkResolver     func(string) (string, error)
		expectedError       error
		expectedMounts      []discover.Mount
		expectedMountsError error
		expectedHooks       []discover.Hook
		expectedHooksError  error
	}{
		{
			// TODO: This current resolves to two mounts that are the same.
			// These are deduplicated at a later stage. We could consider deduplicating earlier in the pipeline.
			description: "symlink is resolved to target; mounts and symlink are created",
			moutSpecs: map[csv.MountSpecType][]string{
				"lib": {"/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so"},
				"sym": {"/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so"},
			},
			symlinkLocator: &lookup.LocatorMock{
				LocateFunc: func(path string) ([]string, error) {
					if path == "/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so" {
						return []string{"/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so"}, nil
					}
					return []string{path}, nil
				},
			},
			symlinkChainLocator: &lookup.LocatorMock{
				LocateFunc: func(path string) ([]string, error) {
					if path == "/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so" {
						return []string{"/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so", "/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so"}, nil
					}
					return nil, fmt.Errorf("Unexpected path: %v", path)
				},
			},
			symlinkResolver: func(path string) (string, error) {
				if path == "/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so" {
					return "/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so", nil
				}
				return path, nil
			},
			expectedMounts: []discover.Mount{
				{
					Path:     "/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so",
					HostPath: "/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so",
					Options:  []string{"ro", "nosuid", "nodev", "bind"},
				},
				{
					Path:     "/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so",
					HostPath: "/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so",
					Options:  []string{"ro", "nosuid", "nodev", "bind"},
				},
			},
			expectedHooks: []discover.Hook{
				{
					Lifecycle: "createContainer",
					Path:      "/usr/bin/nvidia-ctk",
					Args: []string{
						"nvidia-ctk",
						"hook",
						"create-symlinks",
						"--link",
						"/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so::/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so",
					},
				},
			},
		},
		{
			// TODO: This current resolves to two mounts that are the same.
			// These are deduplicated at a later stage. We could consider deduplicating earlier in the pipeline.
			description: "single glob filter does not remove symlink mounts",
			moutSpecs: map[csv.MountSpecType][]string{
				"lib": {"/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so"},
				"sym": {"/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so"},
			},
			ignorePatterns: []string{"*.so"},
			symlinkLocator: &lookup.LocatorMock{
				LocateFunc: func(path string) ([]string, error) {
					if path == "/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so" {
						return []string{"/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so"}, nil
					}
					return []string{path}, nil
				},
			},
			symlinkChainLocator: &lookup.LocatorMock{
				LocateFunc: func(path string) ([]string, error) {
					if path == "/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so" {
						return []string{"/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so", "/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so"}, nil
					}
					return nil, fmt.Errorf("Unexpected path: %v", path)
				},
			},
			symlinkResolver: func(path string) (string, error) {
				if path == "/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so" {
					return "/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so", nil
				}
				return path, nil
			},
			expectedMounts: []discover.Mount{
				{
					Path:     "/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so",
					HostPath: "/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so",
					Options:  []string{"ro", "nosuid", "nodev", "bind"},
				},
				{
					Path:     "/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so",
					HostPath: "/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so",
					Options:  []string{"ro", "nosuid", "nodev", "bind"},
				},
			},
			expectedHooks: []discover.Hook{
				{
					Lifecycle: "createContainer",
					Path:      "/usr/bin/nvidia-ctk",
					Args: []string{
						"nvidia-ctk",
						"hook",
						"create-symlinks",
						"--link",
						"/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so::/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so",
					},
				},
			},
		},
		{
			description: "** filter removes symlink mounts",
			moutSpecs: map[csv.MountSpecType][]string{
				"lib": {"/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so"},
				"sym": {"/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so"},
			},
			symlinkLocator: &lookup.LocatorMock{
				LocateFunc: func(path string) ([]string, error) {
					if path == "/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so" {
						return []string{"/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so"}, nil
					}
					return []string{path}, nil
				},
			},
			ignorePatterns: []string{"**/*.so"},
			expectedMounts: []discover.Mount{
				{
					Path:     "/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so",
					HostPath: "/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so",
					Options:  []string{"ro", "nosuid", "nodev", "bind"},
				},
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.description, func(t *testing.T) {
			defer setGetTargetsFromCSVFiles(tc.moutSpecs)()

			o := tegraOptions{
				logger:              logger,
				nvidiaCTKPath:       "/usr/bin/nvidia-ctk",
				csvFiles:            []string{"dummy"},
				ignorePatterns:      tc.ignorePatterns,
				symlinkLocator:      tc.symlinkLocator,
				symlinkChainLocator: tc.symlinkChainLocator,
				resolveSymlink:      tc.symlinkResolver,
			}

			d, err := o.newDiscovererFromCSVFiles()
			require.ErrorIs(t, err, tc.expectedError)

			hooks, err := d.Hooks()
			require.ErrorIs(t, err, tc.expectedHooksError)
			require.EqualValues(t, tc.expectedHooks, hooks)

			mounts, err := d.Mounts()
			require.ErrorIs(t, err, tc.expectedMountsError)
			require.EqualValues(t, tc.expectedMounts, mounts)

		})
	}
}

func setGetTargetsFromCSVFiles(ovverride map[csv.MountSpecType][]string) func() {
	original := getTargetsFromCSVFiles
	getTargetsFromCSVFiles = func(logger logger.Interface, files []string) map[csv.MountSpecType][]string {
		return ovverride
	}

	return func() {
		getTargetsFromCSVFiles = original
	}
}
