package operating_system

// |@@| C

import (
	"context"
	"gardening/src/lib"
	erro "gardening/src/lib/error"
	"github.com/spf13/afero"
	"io"
	"io/fs"
	"os"
	"path/filepath"
)

type MemoryOperatingSystemIpl struct {
	Data struct {
		Fs           afero.Fs
		MountedDirs  []string
		MountedFiles []string
	}
}

func (this *MemoryOperatingSystemIpl) Kind() string {
	return "memory"
}

func (this *MemoryOperatingSystemIpl) Build() {
	this.Data.Fs = this.MakeFs(context.Background())
}

func (this *MemoryOperatingSystemIpl) MustRebuild(ctx context.Context) {
	this.Data.Fs = afero.NewMemMapFs()
	for _, path := range this.Data.MountedDirs {
		this.MustMountDirectoryToMemory(ctx, path)
	}
	for _, path := range this.Data.MountedFiles {
		this.MustMountFileToMemory(ctx, path)
	}
}

func (this *MemoryOperatingSystemIpl) SetFs(ctx context.Context, fs afero.Fs) {
	this.Data.Fs = fs
}

func (this *MemoryOperatingSystemIpl) GetFs(ctx context.Context) afero.Fs {
	return this.Data.Fs
}

func (this *MemoryOperatingSystemIpl) MakeFs(ctx context.Context) afero.Fs {
	return afero.NewMemMapFs()
}

func (this *MemoryOperatingSystemIpl) Getwd(ctx context.Context) (string, error) {
	return os.Getwd()
}

func (this *MemoryOperatingSystemIpl) Getenv(ctx context.Context, key string) string {
	return os.Getenv(key)
}

func (this *MemoryOperatingSystemIpl) Setenv(ctx context.Context, key string, value string) error {
	return os.Setenv(key, value)
}

func (this *MemoryOperatingSystemIpl) LookupEnv(ctx context.Context, key string) (string, bool) {
	return os.LookupEnv(key)
}

func (this *MemoryOperatingSystemIpl) Remove(ctx context.Context, name string) error {
	return this.Data.Fs.Remove(name)
}

func (this *MemoryOperatingSystemIpl) RemoveAll(ctx context.Context, name string) error {
	return this.Data.Fs.RemoveAll(name)
}

func (this *MemoryOperatingSystemIpl) WriteFile(ctx context.Context, name string, data []byte, perm os.FileMode) error {
	return afero.WriteFile(this.Data.Fs, name, data, perm)
}

// dirEntryWrapper wraps afero.FileInfo to implement fs.DirEntry
type dirEntryWrapper struct {
	os.FileInfo
}

// Info returns the underlying FileInfo
func (d dirEntryWrapper) Info() (fs.FileInfo, error) {
	return d.FileInfo, nil
}

type fileInfoToDirEntry struct {
	info os.FileInfo
}

func (f fileInfoToDirEntry) Name() string {
	return f.info.Name()
}

func (f fileInfoToDirEntry) IsDir() bool {
	return f.info.IsDir()
}

func (f fileInfoToDirEntry) Type() os.FileMode {
	return f.info.Mode().Type()
}

func (f fileInfoToDirEntry) Info() (os.FileInfo, error) {
	return f.info, nil
}

func (this *MemoryOperatingSystemIpl) ReadDir(ctx context.Context, name string) ([]os.DirEntry, error) {
	fileInfos, err := afero.ReadDir(this.Data.Fs, name)
	if err != nil {
		exist := this.checkIfExistDirectly(name)
		s := ""
		if exist {
			s = " (in memory, but name exists on direct os)"
		}
		return nil, erro.W("Cannot read dir"+s, err).KV("name", name).KV("direct_existence", exist)
	}
	dirEntries := make([]os.DirEntry, len(fileInfos))
	for i, fileInfo := range fileInfos {
		dirEntries[i] = fileInfoToDirEntry{info: fileInfo}
	}
	return dirEntries, nil
}

func (this *MemoryOperatingSystemIpl) checkIfExistDirectly(name string) bool {
	_, err := os.Stat(name)
	return !os.IsNotExist(err)
}

func (this *MemoryOperatingSystemIpl) Stat(ctx context.Context, name string) (os.FileInfo, error) {
	return this.Data.Fs.Stat(name)
}

func (this *MemoryOperatingSystemIpl) IsNotExist(ctx context.Context, err error) bool {
	return os.IsNotExist(err)
}

func (this *MemoryOperatingSystemIpl) ReadFile(ctx context.Context, name string) ([]byte, error) {
	b, err := afero.ReadFile(this.Data.Fs, name)
	if err != nil {
		return nil, erro.W("Cannot read file", err).KV("name", name)
	}
	return b, nil
}

func (this *MemoryOperatingSystemIpl) MkdirAll(ctx context.Context, path string, perm os.FileMode) error {
	return this.Data.Fs.MkdirAll(path, perm)
}

func (this *MemoryOperatingSystemIpl) Rename(ctx context.Context, oldpath, newpath string) error {
	return this.Data.Fs.Rename(oldpath, newpath)
}

func (this *MemoryOperatingSystemIpl) MustMountDirectoryToMemory(ctx context.Context, sourceDir string) {
	err := this.MountDirectoryToMemory(ctx, sourceDir)
	lib.Poe(err)
}

func (this *MemoryOperatingSystemIpl) MustMountFileToMemory(ctx context.Context, path string) {
	err := this.MountFileToMemory(ctx, path)
	lib.Poe(err)
}

func (this *MemoryOperatingSystemIpl) MountFileToMemory(ctx context.Context, path string) error {
	this.Data.MountedFiles = append(this.Data.MountedFiles, path)

	srcFile, err := os.Open(path)
	if err != nil {
		return err
	}
	defer srcFile.Close()

	fileInfo, err := srcFile.Stat()
	if err != nil {
		return err
	}

	content, err := io.ReadAll(srcFile)
	if err != nil {
		return err
	}
	return this.WriteFile(ctx, path, content, fileInfo.Mode().Perm())
}

func (this *MemoryOperatingSystemIpl) MountDirectoryToMemory(ctx context.Context, sourceDir string) error {
	this.Data.MountedDirs = append(this.Data.MountedDirs, sourceDir)

	return filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() {
			return this.MkdirAll(ctx, path, info.Mode())
		}

		srcFile, err := os.Open(path)
		if err != nil {
			return err
		}
		defer srcFile.Close()

		content, err := io.ReadAll(srcFile)
		if err != nil {
			return err
		}
		return this.WriteFile(ctx, path, content, info.Mode())
	})
}

func (this *MemoryOperatingSystemIpl) TransferDirFromMemory(ctx context.Context, destDir string) error {
	err := os.RemoveAll(destDir)
	if err != nil {
		return err
	}
	err = os.MkdirAll(destDir, 0744)
	if err != nil {
		return err
	}
	return this.Walk(ctx, destDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() {
			return os.MkdirAll(path, 0744)
		}

		content, err := this.ReadFile(ctx, path)
		if err != nil {
			return err
		}

		err = os.WriteFile(path, content, 0644)
		if err != nil {
			return err
		}
		return err
	})
}

func (this *MemoryOperatingSystemIpl) Walk(ctx context.Context, root string, fn filepath.WalkFunc) error {
	return afero.Walk(this.Data.Fs, root, fn)
}

func (this *MemoryOperatingSystemIpl) Chmod(ctx context.Context, name string, mode os.FileMode) error {
	return this.Data.Fs.Chmod(name, mode)
}
