package archive

import (
	"archive/tar"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/moby/sys/userns"
	"golang.org/x/sys/unix"
)

func getWhiteoutConverter(format WhiteoutFormat) tarWhiteoutConverter {
	if format == OverlayWhiteoutFormat {
		return overlayWhiteoutConverter{}
	}
	return nil
}

type overlayWhiteoutConverter struct{}

func (overlayWhiteoutConverter) ConvertWrite(hdr *tar.Header, path string, fi os.FileInfo) (wo *tar.Header, _ error) {
	// convert whiteouts to AUFS format
	if fi.Mode()&os.ModeCharDevice != 0 && hdr.Devmajor == 0 && hdr.Devminor == 0 {
		// we just rename the file and make it normal
		dir, filename := filepath.Split(hdr.Name)
		hdr.Name = filepath.Join(dir, WhiteoutPrefix+filename)
		hdr.Mode = 0o600
		hdr.Typeflag = tar.TypeReg
		hdr.Size = 0
	}

	if fi.Mode()&os.ModeDir == 0 {
		// FIXME(thaJeztah): return a sentinel error instead of nil, nil
		return nil, nil
	}

	opaqueXattrName := "trusted.overlay.opaque"
	if userns.RunningInUserNS() {
		opaqueXattrName = "user.overlay.opaque"
	}

	// convert opaque dirs to AUFS format by writing an empty file with the prefix
	opaque, err := lgetxattr(path, opaqueXattrName)
	if err != nil {
		return nil, err
	}
	if len(opaque) != 1 || opaque[0] != 'y' {
		// FIXME(thaJeztah): return a sentinel error instead of nil, nil
		return nil, nil
	}
	delete(hdr.PAXRecords, paxSchilyXattr+opaqueXattrName)

	// create a header for the whiteout file
	// it should inherit some properties from the parent, but be a regular file
	return &tar.Header{
		Typeflag:   tar.TypeReg,
		Mode:       hdr.Mode & int64(os.ModePerm),
		Name:       filepath.Join(hdr.Name, WhiteoutOpaqueDir), // #nosec G305 -- An archive is being created, not extracted.
		Size:       0,
		Uid:        hdr.Uid,
		Uname:      hdr.Uname,
		Gid:        hdr.Gid,
		Gname:      hdr.Gname,
		AccessTime: hdr.AccessTime,
		ChangeTime: hdr.ChangeTime,
	}, nil
}

func (c overlayWhiteoutConverter) ConvertRead(hdr *tar.Header, path string) (bool, error) {
	base := filepath.Base(path)
	dir := filepath.Dir(path)

	// if a directory is marked as opaque by the AUFS special file, we need to translate that to overlay
	if base == WhiteoutOpaqueDir {
		opaqueXattrName := "trusted.overlay.opaque"
		if userns.RunningInUserNS() {
			opaqueXattrName = "user.overlay.opaque"
		}

		err := unix.Setxattr(dir, opaqueXattrName, []byte{'y'}, 0)
		if err != nil {
			return false, fmt.Errorf("setxattr('%s', %s=y): %w", dir, opaqueXattrName, err)
		}
		// don't write the file itself
		return false, err
	}

	// if a file was deleted and we are using overlay, we need to create a character device
	if strings.HasPrefix(base, WhiteoutPrefix) {
		originalBase := base[len(WhiteoutPrefix):]
		originalPath := filepath.Join(dir, originalBase)

		if err := unix.Mknod(originalPath, unix.S_IFCHR, 0); err != nil {
			return false, fmt.Errorf("failed to mknod('%s', S_IFCHR, 0): %w", originalPath, err)
		}
		if err := os.Chown(originalPath, hdr.Uid, hdr.Gid); err != nil {
			return false, err
		}

		// don't write the file itself
		return false, nil
	}

	return true, nil
}
