// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.

package resource

import (
	"encoding/json"
_	"fmt"
	"gopkg.in/yaml.v3"
	"io"
	"log/slog"
_	"net/url"
	"github.com/sters/yaml-diff/yamldiff"
	"strings"
)

type Document struct {
	ResourceDecls []Declaration `json:"resources" yaml:"resources"`
}

func NewDocument() *Document {
	return &Document{}
}

func (d *Document) Filter(filter ResourceSelector) []*Declaration {
	resources := make([]*Declaration, 0, len(d.ResourceDecls))
	for i := range d.ResourceDecls {
		filterResource := &d.ResourceDecls[i]
		if filter == nil || filter(filterResource) {
			resources = append(resources, &d.ResourceDecls[i])
		}
	}
	return resources
}

func (d *Document) Clone() *Document {
	clone := NewDocument()
	clone.ResourceDecls = make([]Declaration, len(d.ResourceDecls))
	for i, res := range d.ResourceDecls {
		clone.ResourceDecls[i] = *res.Clone()
	}
	return clone
}

func (d *Document) Load(r io.Reader) error {
	c := NewYAMLDecoder(r)
	return c.Decode(d);
}

func (d *Document) Validate() error {
	jsonDocument, jsonErr := d.JSON()
	slog.Info("document.Validate() json", "json", jsonDocument, "err", jsonErr)
	if jsonErr == nil {
		s := NewSchema("document")
		err := s.Validate(string(jsonDocument))
		if err != nil {
			return err
		}
/*
		for i := range d.ResourceDecls {
			if e := d.ResourceDecls[i].Resource().Validate(); e != nil {
				return fmt.Errorf("failed to validate resource %s; %w", d.ResourceDecls[i].Resource().URI(), e)
			}
		}
*/
	}
	return nil
}

func (d *Document) Resources() []Declaration {
	return d.ResourceDecls
}

func (d *Document) Apply() error {
	if d == nil {
		panic("Undefined Document")
	}
	slog.Info("Document.Apply()", "declarations", d)
	for i := range d.ResourceDecls {
		if e := d.ResourceDecls[i].Resource().Apply(); e != nil {
			return e
		}
	}
	return nil
}

func (d *Document) Generate(w io.Writer) error {
	e := NewYAMLEncoder(w)
	err := e.Encode(d);
	if err == nil {
		return e.Close()
	}
	e.Close()
	return err
}

func (d *Document) AddResourceDeclaration(resourceType string, resourceDeclaration Resource) {
	decl := NewDeclaration()
	decl.Type = TypeName(resourceType)
	decl.Attributes = resourceDeclaration
	d.ResourceDecls = append(d.ResourceDecls, *decl)
}

func (d *Document) AddResource(uri string) error {
	//parsedResourceURI, e := url.Parse(uri)
	//if e == nil {
	decl := NewDeclaration()
	if e := decl.SetURI(uri); e != nil {
		return e
	}
	
	d.ResourceDecls = append(d.ResourceDecls, *decl)
	//}
	return nil
}

func (d *Document) JSON() ([]byte, error) {
	return json.Marshal(d)
}

func (d *Document) YAML() ([]byte, error) {
	return yaml.Marshal(d)
}

func (d *Document) Diff(with *Document, output io.Writer) (string, error) {
	opts := []yamldiff.DoOptionFunc{}
	if output == nil {
		output = &strings.Builder{}
	}
	ydata, yerr := d.YAML()
	if yerr != nil {
		return "", yerr
	}

	yamlDiff,yamlDiffErr := yamldiff.Load(string(ydata))
	if yamlDiffErr != nil {
		return "", yamlDiffErr
	}
	
	wdata,werr := with.YAML()
	if werr != nil {
		return "", werr
	}
	withDiff,withDiffErr := yamldiff.Load(string(wdata))
	if withDiffErr != nil {
		return "", withDiffErr
	}
	
	for _,diff := range yamldiff.Do(yamlDiff, withDiff, opts...) {
		slog.Info("Diff()", "diff", diff)
		_,e := output.Write([]byte(diff.Dump()))
		if e != nil {
			return "", e
		}
	}
	slog.Info("Document.Diff() ", "document.yaml", ydata, "with.yaml", wdata)
	if stringOutput, ok := output.(*strings.Builder); ok {
		return stringOutput.String(), nil
	}
	return "", nil
}
