/*
   Copyright The containerd Authors.

   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 erofsutils

import (
	"context"
	"fmt"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/containerd/containerd/v2/core/mount"
	"github.com/containerd/errdefs"
	"github.com/containerd/log"
)

func ConvertTarErofs(ctx context.Context, r io.Reader, layerPath, uuid string, mkfsExtraOpts []string) error {
	args := append([]string{"--tar=f", "--aufs", "--quiet", "-Enoinline_data"}, mkfsExtraOpts...)
	if uuid != "" {
		args = append(args, []string{"-U", uuid}...)
	}
	args = append(args, layerPath)
	cmd := exec.CommandContext(ctx, "mkfs.erofs", args...)
	cmd.Stdin = r
	out, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("erofs apply failed: %s: %w", out, err)
	}
	log.G(ctx).Infof("running %s %s %v", cmd.Path, cmd.Args, string(out))
	return nil
}

func ConvertErofs(ctx context.Context, layerPath string, srcDir string, mkfsExtraOpts []string) error {
	args := append([]string{"--quiet", "-Enoinline_data"}, mkfsExtraOpts...)
	args = append(args, layerPath, srcDir)
	cmd := exec.CommandContext(ctx, "mkfs.erofs", args...)
	out, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("erofs apply failed: %s: %w", out, err)
	}
	log.G(ctx).Infof("running %s %s %v", cmd.Path, cmd.Args, string(out))
	return nil
}

// MountsToLayer returns the snapshot layer directory in order to generate
// EROFS-formatted blobs;
//
// If mount[0].Type is `bind` or `erofs`, it just tries the source dir; Or if
// mount[0].Type is `overlayfs`, it tries the parent of the upperdir;
//
// The candidate will be checked with ".erofslayer" to make sure this active
// snapshot is really generated by the EROFS snapshotter instead of others.
func MountsToLayer(mounts []mount.Mount) (string, error) {
	var layer string
	switch mnt := mounts[0]; mnt.Type {
	case "bind", "erofs":
		layer = filepath.Dir(mnt.Source)
	case "overlay":
		var topLower string
		for _, o := range mnt.Options {
			if k, v, ok := strings.Cut(o, "="); ok {
				switch k {
				case "upperdir":
					layer = filepath.Dir(v)
				case "lowerdir":
					dir, _, _ := strings.Cut(v, ":")
					topLower = filepath.Dir(dir)
				}
			}
		}
		if layer == "" {
			if topLower == "" {
				return "", fmt.Errorf("unsupported overlay layer for erofs differ: %w", errdefs.ErrNotImplemented)
			}
			layer = topLower
		}
	default:
		return "", fmt.Errorf("invalid filesystem type for erofs differ: %w", errdefs.ErrNotImplemented)
	}
	// If the layer is not prepared by the EROFS snapshotter, fall back to the next differ
	if _, err := os.Stat(filepath.Join(layer, ".erofslayer")); err != nil {
		return "", fmt.Errorf("mount layer type must be erofs-layer: %w", errdefs.ErrNotImplemented)
	}
	return layer, nil
}
