/*
   Copyright © 2022 - 2025 SUSE LLC

   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 action_test

import (
	"bytes"
	"fmt"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/sirupsen/logrus"
	"github.com/twpayne/go-vfs/v4"
	"github.com/twpayne/go-vfs/v4/vfst"

	"github.com/rancher/elemental-toolkit/v2/pkg/action"
	"github.com/rancher/elemental-toolkit/v2/pkg/config"
	"github.com/rancher/elemental-toolkit/v2/pkg/constants"
	"github.com/rancher/elemental-toolkit/v2/pkg/features"
	"github.com/rancher/elemental-toolkit/v2/pkg/mocks"
	"github.com/rancher/elemental-toolkit/v2/pkg/types"
	"github.com/rancher/elemental-toolkit/v2/pkg/utils"
)

var _ = Describe("Init Action", func() {
	var cfg *types.RunConfig
	var runner *mocks.FakeRunner
	var fs vfs.FS
	var logger types.Logger
	var cleanup func()
	var memLog *bytes.Buffer
	var expectedNumUnits int
	var spec *types.InitSpec
	var enabledUnits []string
	var errCmd, initrdFile string
	var mounter *mocks.FakeMounter

	BeforeEach(func() {
		mounter = mocks.NewFakeMounter()
		runner = mocks.NewFakeRunner()
		memLog = &bytes.Buffer{}
		logger = types.NewBufferLogger(memLog)
		logger.SetLevel(logrus.DebugLevel)
		fs, cleanup, _ = vfst.NewTestFS(map[string]interface{}{})
		cfg = config.NewRunConfig(
			config.WithFs(fs),
			config.WithRunner(runner),
			config.WithMounter(mounter),
			config.WithLogger(logger),
		)

		feats, err := features.Get(features.All)
		Expect(err).To(BeNil())

		expectedNumUnits = 0
		for _, feat := range feats {
			expectedNumUnits += len(feat.Units)
		}

		spec = config.NewInitSpec()
		enabledUnits = []string{}
		initrdFile = "/boot/elemental.initrd-6.4"

		// Emulate running in a dockerenv
		Expect(fs.WriteFile("/.dockerenv", []byte{}, 0644)).To(Succeed())

		runner.SideEffect = func(cmd string, args ...string) ([]byte, error) {
			if cmd == errCmd {
				return []byte{}, fmt.Errorf("failed calling %s", cmd)
			}
			switch cmd {
			case "systemctl":
				if args[0] == "enable" {
					enabledUnits = append(enabledUnits, args[1])
				}
				return []byte{}, nil
			case "dracut":
				_, err := fs.Create(initrdFile)
				Expect(err).To(Succeed())
				return []byte{}, nil
			default:
				return []byte{}, nil
			}
		}

		// Create a kernel file and modules folder
		Expect(utils.MkdirAll(fs, "/lib/modules/6.4", constants.DirPerm)).To(Succeed())
		Expect(utils.MkdirAll(fs, "/boot", constants.DirPerm)).To(Succeed())
		_, err = fs.Create("/boot/vmlinuz-6.4")
		Expect(err).To(Succeed())
	})
	AfterEach(func() {
		cleanup()
	})

	Describe("Init action", Label("init"), func() {
		It("Shows an error if /.dockerenv does not exist", func() {
			Expect(fs.Remove("/.dockerenv")).To(Succeed())
			Expect(action.RunInit(cfg, spec)).ToNot(Succeed())
			Expect(len(enabledUnits)).To(Equal(0))
		})
		It("Successfully runs init and install files", func() {
			preExistingInitrd := "/boot/elemental.initrd-6.2"
			_, err := fs.Create(preExistingInitrd)
			Expect(err).NotTo(HaveOccurred())

			Expect(action.RunInit(cfg, spec)).To(Succeed())

			Expect(len(enabledUnits)).To(Equal(expectedNumUnits))

			for _, unit := range enabledUnits {
				exists, err := utils.Exists(fs, fmt.Sprintf("/etc/systemd/system/%v", unit))
				Expect(err).To(BeNil())
				Expect(exists).To(BeTrue())
			}

			exists, _ := utils.Exists(fs, "/boot/elemental.initrd-6.4")
			Expect(exists).To(BeTrue())

			exists, _ = utils.Exists(fs, preExistingInitrd)
			Expect(exists).To(BeFalse())

			// Check expected initrd and kernel files are created
			exists, _ = utils.Exists(fs, "/boot/vmlinuz")
			Expect(exists).To(BeTrue())
			exists, _ = utils.Exists(fs, "/boot/initrd")
			Expect(exists).To(BeTrue())
		})
		It("fails if requested feature does not exist", func() {
			spec.Features = append(spec.Features, "nonexisting")
			Expect(action.RunInit(cfg, spec)).NotTo(Succeed())
			Expect(len(enabledUnits)).To(Equal(0))
		})
		It("fails if the kernel file is not there", func() {
			Expect(fs.Remove("/boot/vmlinuz-6.4")).To(Succeed())
			Expect(action.RunInit(cfg, spec)).NotTo(Succeed())

			Expect(len(enabledUnits)).To(Equal(expectedNumUnits))

			for _, unit := range enabledUnits {
				exists, err := utils.Exists(fs, fmt.Sprintf("/etc/systemd/system/%v", unit))
				Expect(err).To(BeNil())
				Expect(exists).To(BeTrue())
			}
		})
		It("fails on dracut call", func() {
			errCmd = "dracut"
			Expect(action.RunInit(cfg, spec)).NotTo(Succeed())

			Expect(len(enabledUnits)).To(Equal(expectedNumUnits))

			for _, unit := range enabledUnits {
				exists, err := utils.Exists(fs, fmt.Sprintf("/etc/systemd/system/%v", unit))
				Expect(err).To(BeNil())
				Expect(exists).To(BeTrue())
			}
		})
		It("fails if initrd is not found", func() {
			initrdFile = "/boot/wrongInird"
			Expect(action.RunInit(cfg, spec)).NotTo(Succeed())

			Expect(len(enabledUnits)).To(Equal(expectedNumUnits))

			for _, unit := range enabledUnits {
				exists, err := utils.Exists(fs, fmt.Sprintf("/etc/systemd/system/%v", unit))
				Expect(err).To(BeNil())
				Expect(exists).To(BeTrue())
			}
		})
	})
})
