package yqlib

import (
	"bufio"
	"container/list"
	"fmt"
	"os"
)

var LoadYamlPreferences = YamlPreferences{
	LeadingContentPreProcessing: false,
	PrintDocSeparators:          true,
	UnwrapScalar:                true,
	EvaluateTogether:            false,
}

type loadPrefs struct {
	decoder Decoder
}

func loadString(filename string) (*CandidateNode, error) {
	// ignore CWE-22 gosec issue - that's more targeted for http based apps that run in a public directory,
	// and ensuring that it's not possible to give a path to a file outside that directory.

	filebytes, err := os.ReadFile(filename) // #nosec
	if err != nil {
		return nil, err
	}

	return &CandidateNode{Kind: ScalarNode, Tag: "!!str", Value: string(filebytes)}, nil
}

func loadWithDecoder(filename string, decoder Decoder) (*CandidateNode, error) {
	if decoder == nil {
		return nil, fmt.Errorf("could not load %s", filename)
	}

	file, err := os.Open(filename) // #nosec
	if err != nil {
		return nil, err
	}
	reader := bufio.NewReader(file)

	documents, err := readDocuments(reader, filename, 0, decoder)
	if err != nil {
		return nil, err
	}

	if documents.Len() == 0 {
		// return null candidate
		return &CandidateNode{Kind: ScalarNode, Tag: "!!null"}, nil
	} else if documents.Len() == 1 {
		candidate := documents.Front().Value.(*CandidateNode)
		return candidate, nil

	}
	sequenceNode := &CandidateNode{Kind: SequenceNode}
	for doc := documents.Front(); doc != nil; doc = doc.Next() {
		sequenceNode.AddChild(doc.Value.(*CandidateNode))
	}
	return sequenceNode, nil
}

func loadStringOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
	log.Debugf("loadString")

	var results = list.New()

	for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
		candidate := el.Value.(*CandidateNode)

		rhs, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode.RHS)
		if err != nil {
			return Context{}, err
		}
		if rhs.MatchingNodes.Front() == nil {
			return Context{}, fmt.Errorf("filename expression returned nil")
		}
		nameCandidateNode := rhs.MatchingNodes.Front().Value.(*CandidateNode)

		filename := nameCandidateNode.Value

		contentsCandidate, err := loadString(filename)
		if err != nil {
			return Context{}, fmt.Errorf("Failed to load %v: %w", filename, err)
		}

		results.PushBack(contentsCandidate)

	}

	return context.ChildContext(results), nil
}

func loadOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
	log.Debugf("loadOperator")

	loadPrefs := expressionNode.Operation.Preferences.(loadPrefs)

	// need to evaluate the 1st parameter against the context
	// and return the data accordingly.

	var results = list.New()

	for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
		candidate := el.Value.(*CandidateNode)

		rhs, err := d.GetMatchingNodes(context.SingleReadonlyChildContext(candidate), expressionNode.RHS)
		if err != nil {
			return Context{}, err
		}
		if rhs.MatchingNodes.Front() == nil {
			return Context{}, fmt.Errorf("filename expression returned nil")
		}
		nameCandidateNode := rhs.MatchingNodes.Front().Value.(*CandidateNode)

		filename := nameCandidateNode.Value

		contentsCandidate, err := loadWithDecoder(filename, loadPrefs.decoder)
		if err != nil {
			return Context{}, fmt.Errorf("Failed to load %v: %w", filename, err)
		}

		results.PushBack(contentsCandidate)

	}

	return context.ChildContext(results), nil
}
