//go:build windows

package ociwclayer

import (
	"archive/tar"
	"context"
	"errors"
	"io"
	"path/filepath"

	"github.com/Microsoft/go-winio/backuptar"
	"github.com/Microsoft/hcsshim/internal/wclayer"
)

// ExportLayerToTar writes an OCI layer tar stream from the provided on-disk layer.
// The caller must specify the parent layers, if any, ordered from lowest to
// highest layer.
//
// The layer will be mounted for this process, so the caller should ensure that
// it is not currently mounted.
func ExportLayerToTar(ctx context.Context, w io.Writer, path string, parentLayerPaths []string) error {
	err := wclayer.ActivateLayer(ctx, path)
	if err != nil {
		return err
	}
	defer func() {
		_ = wclayer.DeactivateLayer(ctx, path)
	}()

	// Prepare and unprepare the layer to ensure that it has been initialized.
	err = wclayer.PrepareLayer(ctx, path, parentLayerPaths)
	if err != nil {
		return err
	}
	err = wclayer.UnprepareLayer(ctx, path)
	if err != nil {
		return err
	}

	r, err := wclayer.NewLayerReader(ctx, path, parentLayerPaths)
	if err != nil {
		return err
	}

	err = writeTarFromLayer(ctx, r, w)
	cerr := r.Close()
	if err != nil {
		return err
	}
	return cerr
}

func writeTarFromLayer(ctx context.Context, r wclayer.LayerReader, w io.Writer) error {
	linkRecords := make(map[[16]byte]string)

	t := tar.NewWriter(w)
	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
		}

		name, size, fileInfo, err := r.Next()
		if errors.Is(err, io.EOF) {
			break
		}
		if err != nil {
			return err
		}
		if fileInfo == nil {
			// Write a whiteout file.
			hdr := &tar.Header{
				Name: filepath.ToSlash(filepath.Join(filepath.Dir(name), WhiteoutPrefix+filepath.Base(name))),
			}
			err := t.WriteHeader(hdr)
			if err != nil {
				return err
			}
		} else {
			numberOfLinks, fileIDInfo, err := r.LinkInfo()
			if err != nil {
				return err
			}
			if numberOfLinks > 1 {
				if linkName, ok := linkRecords[fileIDInfo.FileID]; ok {
					// We've seen this file before, by another name, so put a hardlink in the tar stream.
					hdr := backuptar.BasicInfoHeader(name, 0, fileInfo)
					hdr.Mode = 0644
					hdr.Typeflag = tar.TypeLink
					hdr.Linkname = linkName
					if err := t.WriteHeader(hdr); err != nil {
						return err
					}
					continue
				}

				// All subsequent names for this file will be hard-linked to this name
				linkRecords[fileIDInfo.FileID] = filepath.ToSlash(name)
			}

			err = backuptar.WriteTarFileFromBackupStream(t, r, name, size, fileInfo)
			if err != nil {
				return err
			}
		}
	}
	return t.Close()
}
