package config

import (
	"os"
	"path/filepath"

	"github.com/ghodss/yaml"
	"github.com/imdario/mergo"
)

// FileName is the name of bundle configuration file.
const FileName = "bundle.yml"

type Root struct {
	// Path contains the directory path to the root of the bundle.
	// It is set when loading `bundle.yml`.
	Path string `json:"-"`

	// Bundle contains details about this bundle, such as its name,
	// version of the spec (TODO), default cluster, default warehouse, etc.
	Bundle Bundle `json:"bundle"`

	// Include specifies a list of patterns of file names to load and
	// merge into the this configuration. If not set in `bundle.yml`,
	// it defaults to loading `*.yml` and `*/*.yml`.
	//
	// Also see [mutator.DefineDefaultInclude].
	//
	Include []string `json:"include,omitempty"`

	// Workspace contains details about the workspace to connect to
	// and paths in the workspace tree to use for this bundle.
	Workspace Workspace `json:"workspace"`

	// Artifacts contains a description of all code artifacts in this bundle.
	Artifacts map[string]*Artifact `json:"artifacts,omitempty"`

	// Resources contains a description of all Databricks resources
	// to deploy in this bundle (e.g. jobs, pipelines, etc.).
	Resources Resources `json:"resources,omitempty"`

	// Environments can be used to differentiate settings and resources between
	// bundle deployment environments (e.g. development, staging, production).
	// If not specified, the code below initializes this field with a
	// single default-initialized environment called "default".
	Environments map[string]*Environment `json:"environments,omitempty"`
}

func Load(path string) (*Root, error) {
	var r Root

	stat, err := os.Stat(path)
	if err != nil {
		return nil, err
	}

	// If we were given a directory, assume this is the bundle root.
	if stat.IsDir() {
		r.Path = path
		path = filepath.Join(path, FileName)
	}

	if err := r.Load(path); err != nil {
		return nil, err
	}

	return &r, nil
}

func (r *Root) Load(file string) error {
	raw, err := os.ReadFile(file)
	if err != nil {
		return err
	}
	err = yaml.Unmarshal(raw, r)
	if err != nil {
		return err
	}
	return nil
}

func (r *Root) Merge(other *Root) error {
	// TODO: define and test semantics for merging.
	return mergo.MergeWithOverwrite(r, other)
}

func (r *Root) MergeEnvironment(env *Environment) error {
	var err error

	// Environment may be nil if it's empty.
	if env == nil {
		return nil
	}

	if env.Bundle != nil {
		err = mergo.MergeWithOverwrite(&r.Bundle, env.Bundle)
		if err != nil {
			return err
		}
	}

	if env.Workspace != nil {
		err = mergo.MergeWithOverwrite(&r.Workspace, env.Workspace)
		if err != nil {
			return err
		}
	}

	if env.Artifacts != nil {
		err = mergo.Merge(&r.Artifacts, env.Artifacts, mergo.WithAppendSlice)
		if err != nil {
			return err
		}
	}

	if env.Resources != nil {
		err = mergo.Merge(&r.Resources, env.Resources, mergo.WithAppendSlice)
		if err != nil {
			return err
		}
	}

	return nil
}