package commands

import (
	gerrors "errors"
	"fmt"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"testing/fstest"

	"cuelang.org/go/cue/errors"
	"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
	"github.com/grafana/grafana/pkg/schema"
	"github.com/grafana/grafana/pkg/schema/load"
)

var paths = load.GetDefaultLoadPaths()

func (cmd Command) validateScuemata(c utils.CommandLine) error {
	root := c.String("grafana-root")
	if root == "" {
		return gerrors.New("must provide path to the root of a Grafana repository checkout")
	}

	// Construct MapFS with the same set of files as those embedded in
	// /embed.go, but sourced straight through from disk instead of relying on
	// what's compiled.  Not the greatest, because we're duplicating
	// filesystem-loading logic with what's in /embed.go.
	var fspaths load.BaseLoadPaths
	var err error

	fspaths.BaseCueFS, err = populateMapFSFromRoot(paths.BaseCueFS, root, "")
	if err != nil {
		return err
	}
	fspaths.DistPluginCueFS, err = populateMapFSFromRoot(paths.DistPluginCueFS, root, "")
	if err != nil {
		return err
	}

	if err := validateScuemata(fspaths, load.DistDashboardFamily); err != nil {
		return schema.WrapCUEError(err)
	}

	return nil
}

// Helper function that populates an fs.FS by walking over a virtual filesystem,
// and reading files from disk corresponding to each file encountered.
func populateMapFSFromRoot(in fs.FS, root, join string) (fs.FS, error) {
	out := make(fstest.MapFS)
	err := fs.WalkDir(in, ".", func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}

		if d.IsDir() {
			return nil
		}
		// Ignore gosec warning G304. The input set here is necessarily
		// constrained to files specified in embed.go
		// nolint:gosec
		b, err := os.Open(filepath.Join(root, join, path))
		if err != nil {
			return err
		}
		byt, err := io.ReadAll(b)
		if err != nil {
			return err
		}

		out[path] = &fstest.MapFile{Data: byt}
		return nil
	})
	return out, err
}

func (cmd Command) validateResources(c utils.CommandLine) error {
	filename := c.String("dashboard")
	baseonly := c.Bool("base-only")
	if filename == "" {
		return gerrors.New("must specify dashboard to validate with --dashboard")
	}
	b, err := os.Open(filepath.Clean(filename))
	res := schema.Resource{Value: b, Name: filename}
	if err != nil {
		return err
	}

	var sch schema.VersionedCueSchema
	if baseonly {
		sch, err = load.BaseDashboardFamily(paths)
	} else {
		sch, err = load.DistDashboardFamily(paths)
	}
	if err != nil {
		return fmt.Errorf("error while loading dashboard scuemata, err: %w", err)
	}

	err = sch.Validate(res)
	if err != nil {
		return gerrors.New(errors.Details(err, nil))
	}
	return nil
}

func validateScuemata(p load.BaseLoadPaths, loader func(p load.BaseLoadPaths) (schema.VersionedCueSchema, error)) error {
	dash, err := loader(p)
	if err != nil {
		return fmt.Errorf("error while loading dashboard scuemata, err: %w", err)
	}

	// Check that a CUE value exists.
	cueValue := dash.CUE()
	if !cueValue.Exists() {
		return fmt.Errorf("cue value for schema does not exist")
	}

	// Check CUE validity.
	if err := cueValue.Validate(); err != nil {
		return fmt.Errorf("all schema should be valid with respect to basic CUE rules, %w", err)
	}

	return nil
}
