package taskfile

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

	"github.com/go-task/task/v3/internal/execext"
	"github.com/go-task/task/v3/internal/filepathext"
	"github.com/go-task/task/v3/internal/logger"
)

// A FileNode is a node that reads a taskfile from the local filesystem.
type FileNode struct {
	*BaseNode
	Entrypoint string
}

func NewFileNode(l *logger.Logger, entrypoint, dir string, opts ...NodeOption) (*FileNode, error) {
	var err error
	base := NewBaseNode(dir, opts...)
	entrypoint, base.dir, err = resolveFileNodeEntrypointAndDir(l, entrypoint, base.dir)
	if err != nil {
		return nil, err
	}
	return &FileNode{
		BaseNode:   base,
		Entrypoint: entrypoint,
	}, nil
}

func (node *FileNode) Location() string {
	return node.Entrypoint
}

func (node *FileNode) Remote() bool {
	return false
}

func (node *FileNode) Read(ctx context.Context) ([]byte, error) {
	f, err := os.Open(node.Location())
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return io.ReadAll(f)
}

// resolveFileNodeEntrypointAndDir resolves checks the values of entrypoint and dir and
// populates them with default values if necessary.
func resolveFileNodeEntrypointAndDir(l *logger.Logger, entrypoint, dir string) (string, string, error) {
	var err error
	if entrypoint != "" {
		entrypoint, err = Exists(l, entrypoint)
		if err != nil {
			return "", "", err
		}
		if dir == "" {
			dir = filepath.Dir(entrypoint)
		}
		return entrypoint, dir, nil
	}
	if dir == "" {
		dir, err = os.Getwd()
		if err != nil {
			return "", "", err
		}
	}
	entrypoint, err = ExistsWalk(l, dir)
	if err != nil {
		return "", "", err
	}
	dir = filepath.Dir(entrypoint)
	return entrypoint, dir, nil
}

func (node *FileNode) ResolveEntrypoint(entrypoint string) (string, error) {
	// If the file is remote, we don't need to resolve the path
	if strings.Contains(entrypoint, "://") {
		return entrypoint, nil
	}

	path, err := execext.Expand(entrypoint)
	if err != nil {
		return "", err
	}

	if filepathext.IsAbs(path) {
		return path, nil
	}

	// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
	// This means that files are included relative to one another
	entrypointDir := filepath.Dir(node.Entrypoint)
	return filepathext.SmartJoin(entrypointDir, path), nil
}

func (node *FileNode) ResolveDir(dir string) (string, error) {
	path, err := execext.Expand(dir)
	if err != nil {
		return "", err
	}

	if filepathext.IsAbs(path) {
		return path, nil
	}

	// NOTE: Uses the directory of the entrypoint (Taskfile), not the current working directory
	// This means that files are included relative to one another
	entrypointDir := filepath.Dir(node.Entrypoint)
	return filepathext.SmartJoin(entrypointDir, path), nil
}

func (node *FileNode) FilenameAndLastDir() (string, string) {
	return "", filepath.Base(node.Entrypoint)
}