// Copyright 2022 CUE 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 export_test

import (
	"bytes"
	"slices"
	"strings"
	"testing"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/ast"
	"cuelang.org/go/cue/build"
	"cuelang.org/go/cue/cuecontext"
	"cuelang.org/go/cue/errors"
	"cuelang.org/go/cue/format"
	"cuelang.org/go/internal/core/export"
	"cuelang.org/go/internal/core/runtime"
	"cuelang.org/go/internal/cuedebug"
	"cuelang.org/go/internal/cuetdtest"
	"cuelang.org/go/internal/cuetxtar"
	"cuelang.org/go/internal/diff"
	"golang.org/x/tools/txtar"
)

func TestSelfContained(t *testing.T) {
	t.Parallel()
	test := cuetxtar.TxTarTest{
		Name: "self",
		Root: "./testdata/selfcontained",

		Matrix: cuetdtest.FullMatrix,

		ToDo: map[string]string{
			"self-v3/selfcontained/import": `wa: reference "_hidden_567475F3" not found:`,

			"self/selfcontained/cyclic":            `reference not properly substituted`,
			"self-v3/selfcontained/cyclic":         `reference not properly substituted`,
			"self-v3-noshare/selfcontained/cyclic": `reference not properly substituted`,
		},
	}

	test.Run(t, func(t *cuetxtar.Test) {
		t.Parallel()
		r := t.CueContext()

		a := t.Instances()

		v := buildFile(t.T, r, a[0])

		p, ok := t.Value("path")
		if ok {
			v = v.LookupPath(cue.ParsePath(p))
		}

		tValue := v.Core()

		self := *export.All
		self.SelfContained = true

		w := t.Writer("default")

		test := func() {
			file, errs := self.Def(tValue.R, "", tValue.V)

			errors.Print(w, errs, nil)
			_, _ = w.Write(formatNode(t.T, file))

			vf := patch(t.T, r, t.Archive, file)
			doDiff(t.T, v, vf)

			v = v.Unify(vf)
			doDiff(t.T, v, vf)
		}
		test()

		if _, ok := t.Value("inlineImports"); ok {
			w = t.Writer("expand_imports")
			self.InlineImports = true
			test()
		}
	})
}

func buildFile(t *testing.T, r *cue.Context, b *build.Instance) cue.Value {
	t.Helper()
	v := r.BuildInstance(b)
	if err := v.Err(); err != nil {
		t.Fatal(errors.Details(err, nil))
	}
	return v
}

// patch replaces the package at the root of the Archive with the given CUE
// file.
func patch(t *testing.T, r *cue.Context, orig *txtar.Archive, f *ast.File) cue.Value {
	a := *orig
	a.Files = slices.Clone(orig.Files)

	k := 0
	for _, f := range a.Files {
		if strings.HasSuffix(f.Name, ".cue") && !strings.ContainsRune(f.Name, '/') {
			continue
		}
		a.Files[k] = f
		k++
	}
	b, err := format.Node(f)
	if err != nil {
		t.Error(err)
	}

	a.Files = append(a.Files, txtar.File{
		Name: "in.cue",
		Data: b,
	})

	instance := cuetxtar.Load(&a, t.TempDir())[0]
	if instance.Err != nil {
		t.Fatal(instance.Err)
	}

	vn := buildFile(t, r, instance)
	if err := vn.Err(); err != nil {
		t.Fatal(err)
	}
	return vn
}

func doDiff(t *testing.T, v, w cue.Value) {
	var bb bytes.Buffer
	p := *diff.Schema
	p.SkipHidden = true
	d, script := p.Diff(v, w)
	if d != diff.Identity {
		diff.Print(&bb, script)
		t.Error(bb.String())
	}
}

// TestSC is for debugging purposes. Do not delete.
func TestSC(t *testing.T) {
	in := `
-- cue.mod/module.cue --
module: "example.com"
language: version: "v0.9.0"
-- in.cue --
	`
	if strings.HasSuffix(strings.TrimSpace(in), ".cue --") {
		t.Skip()
	}

	a := txtar.Parse([]byte(in))
	instance := cuetxtar.Load(a, t.TempDir())[0]
	if instance.Err != nil {
		t.Fatal(instance.Err)
	}

	version := cuecontext.EvalDefault
	r := cuecontext.New(cuecontext.EvaluatorVersion(version))
	(*runtime.Runtime)(r).SetDebugOptions(&cuedebug.Config{
		Sharing: true,
		LogEval: 1,
	})

	v := buildFile(t, r, instance)

	v = v.LookupPath(cue.ParsePath("a.b"))

	tValue := v.Core()
	self := export.All
	self.SelfContained = true
	self.InlineImports = true

	file, errs := self.Def(tValue.R, "", tValue.V)
	if errs != nil {
		t.Fatal(errs)
	}

	b, _ := format.Node(file)
	t.Error(string(b))

	vf := patch(t, r, a, file)
	doDiff(t, v, vf)
}
