//go:build windows
// +build windows

package cimfs

import (
	"bytes"
	"context"
	"encoding/binary"
	"fmt"
	"os"
	"path/filepath"

	"github.com/Microsoft/hcsshim/internal/log"
	"github.com/Microsoft/hcsshim/pkg/cimfs/format"
)

const (
	cimHashSize = 32 // size of a hash of a verified CIM in bytes
)

var (
	// Equivalent to SDDL of "D:NO_ACCESS_CONTROL".
	nullSd = []byte{1, 0, 4, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
)

type OpError struct {
	Cim string
	Op  string
	Err error
}

func (e *OpError) Error() string {
	s := "cim " + e.Op + " " + e.Cim
	s += ": " + e.Err.Error()
	return s
}

// PathError is the error type returned by most functions in this package.
type PathError struct {
	Cim  string
	Op   string
	Path string
	Err  error
}

func (e *PathError) Error() string {
	s := "cim " + e.Op + " " + e.Cim
	s += ":" + e.Path
	s += ": " + e.Err.Error()
	return s
}

type LinkError struct {
	Cim string
	Op  string
	Old string
	New string
	Err error
}

func (e *LinkError) Error() string {
	return "cim " + e.Op + " " + e.Old + " " + e.New + ": " + e.Err.Error()
}

func validateHeader(h *format.CommonHeader) error {
	if !bytes.Equal(h.Magic[:], format.MagicValue[:]) {
		return fmt.Errorf("not a cim file")
	}
	if h.Version.Major > format.CurrentVersion.Major || h.Version.Major < format.MinSupportedVersion.Major {
		return fmt.Errorf("unsupported cim version. cim version %v must be between %v & %v", h.Version, format.MinSupportedVersion, format.CurrentVersion)
	}
	return nil
}

func readFilesystemHeader(f *os.File) (format.FilesystemHeader, error) {
	var fsh format.FilesystemHeader

	if err := binary.Read(f, binary.LittleEndian, &fsh); err != nil {
		return fsh, fmt.Errorf("reading filesystem header: %w", err)
	}

	if err := validateHeader(&fsh.Common); err != nil {
		return fsh, fmt.Errorf("validating filesystem header: %w", err)
	}
	return fsh, nil
}

// Returns the paths of all the objectID files associated with the cim at `cimPath`.
func getObjectIDFilePaths(ctx context.Context, cimPath string) ([]string, error) {
	f, err := os.Open(cimPath)
	if err != nil {
		return []string{}, fmt.Errorf("open cim file %s: %w", cimPath, err)
	}
	defer f.Close()

	fsh, err := readFilesystemHeader(f)
	if err != nil {
		return []string{}, fmt.Errorf("readingp cim header: %w", err)
	}

	paths := []string{}
	for i := 0; i < int(fsh.Regions.Count); i++ {
		path := filepath.Join(filepath.Dir(cimPath), fmt.Sprintf("%s_%v_%d", format.ObjectIDFileName, fsh.Regions.ID, i))
		if _, err := os.Stat(path); err == nil {
			paths = append(paths, path)
		} else {
			log.G(ctx).WithError(err).Warnf("stat for object file %s", path)
		}

	}
	return paths, nil
}

// Returns the paths of all the region files associated with the cim at `cimPath`.
func getRegionFilePaths(ctx context.Context, cimPath string) ([]string, error) {
	f, err := os.Open(cimPath)
	if err != nil {
		return []string{}, fmt.Errorf("open cim file %s: %w", cimPath, err)
	}
	defer f.Close()

	fsh, err := readFilesystemHeader(f)
	if err != nil {
		return []string{}, fmt.Errorf("reading cim header: %w", err)
	}

	paths := []string{}
	for i := 0; i < int(fsh.Regions.Count); i++ {
		path := filepath.Join(filepath.Dir(cimPath), fmt.Sprintf("%s_%v_%d", format.RegionFileName, fsh.Regions.ID, i))
		if _, err := os.Stat(path); err == nil {
			paths = append(paths, path)
		} else {
			log.G(ctx).WithError(err).Warnf("stat for region file %s", path)
		}
	}
	return paths, nil
}
