package setup

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"os"
	"path/filepath"

	goccyyaml "github.com/goccy/go-yaml"
	"gopkg.in/yaml.v3"

	"github.com/crowdsecurity/crowdsec/pkg/acquisition"
	"github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration"
)

var (
	ErrEmptyDatasourceConfig      = errors.New("datasource configuration is empty")
	ErrMissingSourceField         = errors.New("source field is required")
	ErrMissingAcquisitionFilename = errors.New("a filename for the datasource configuration is required")
	ErrInvalidAcquisitionFilename = errors.New("acquisition filename must not contain slashes (/) or backslashes (\\)")
)

// Validate runs static checks on the configuration, but does not guarantee that
// the datasource will be initialized correctly at runtime (may require network connections, etc).
func (d DatasourceConfig) Validate() error {
	if len(d) == 0 {
		return ErrEmptyDatasourceConfig
	}

	// formally validate YAML and common fields

	commonDS := configuration.DataSourceCommonCfg{}

	body, err := yaml.Marshal(d)
	if err != nil {
		return err
	}

	err = yaml.Unmarshal(body, &commonDS)
	if err != nil {
		return err
	}

	if commonDS.Source == "" {
		return ErrMissingSourceField
	}

	ds, err := acquisition.GetDataSourceIface(commonDS.Source)
	if err != nil {
		return err
	}

	// validate the rest of the fields with the concrete implementation

	err = ds.UnmarshalConfig(body)
	if err != nil {
		return err
	}

	return nil
}

// Path returns the path where the acquisition spec will be written.
// The "setup." prefix is added purely as visual hint and for grouping generated files in the directory listing.
func (a *AcquisitionSpec) Path(toDir string) (string, error) {
	// Filename has already been checked with Validate()
	return filepath.Join(toDir, "setup."+a.Filename), nil
}

// Open creates or truncates the acquisition file and returns it opened for writing.
func (a *AcquisitionSpec) Open(toDir string) (*os.File, error) {
	path, err := a.Path(toDir)
	if err != nil {
		return nil, err
	}

	if err := os.MkdirAll(toDir, 0o700); err != nil {
		return nil, fmt.Errorf("creating acquisition directory: %w", err)
	}

	f, err := os.Create(path)
	if err != nil {
		return nil, fmt.Errorf("creating acquisition file: %w", err)
	}

	return f, nil
}

func (a *AcquisitionSpec) ToYAML() ([]byte, error) {
	out, err := goccyyaml.MarshalWithOptions(a.Datasource, goccyyaml.IndentSequence(true))
	if err != nil {
		return nil, fmt.Errorf("while encoding datasource: %w", err)
	}

	return out, nil
}

func (*AcquisitionSpec) AddHeader(content []byte) []byte {
	ret := bytes.NewBuffer(nil)
	ret.WriteString(`#
# Configuration generated by "cscli setup".
# Please check your non-generated configuration files to make sure
# the log sources are not acquired twice. This includes
# the file acquis.yaml created by crowdsec <= 1.7.0.
#
`)

	hash := sha256.Sum256(content)
	checksum := hex.EncodeToString(hash[:16])

	ret.WriteString("# cscli-checksum: " + checksum + "\n\n")
	ret.Write(content)

	return ret.Bytes()
}
