package output

import (
	"encoding/json"
	"fmt"
	"os"

	"github.com/snyk/driftctl/pkg/analyser"
	"github.com/snyk/driftctl/pkg/resource"
)

const FormatVersion = "0.1"
const PlanOutputType = "plan"
const PlanOutputExample = "plan://PATH/TO/FILE.json"

type plan struct {
	FormatVersion   string        `json:"format_version,omitempty"`
	PlannedValues   plannedValues `json:"planned_values,omitempty"`
	ResourceChanges []rscChange   `json:"resource_changes,omitempty"`
}

type plannedValues struct {
	RootModule module `json:"root_module,omitempty"`
}

type rscChange struct {
	Address string `json:"address,omitempty"`
	Type    string `json:"type,omitempty"`
	Name    string `json:"name,omitempty"`
	Change  change `json:"change,omitempty"`
}

type change struct {
	Actions []string               `json:"actions,omitempty"`
	Before  map[string]interface{} `json:"before,omitempty"`
	After   map[string]interface{} `json:"after,omitempty"`
}

type module struct {
	Resources []rsc `json:"resources,omitempty"`
}

type rsc struct {
	Address         string                 `json:"address,omitempty"`
	Type            string                 `json:"type,omitempty"`
	Name            string                 `json:"name,omitempty"`
	AttributeValues map[string]interface{} `json:"values,omitempty"`
}

type Plan struct {
	path string
}

func NewPlan(path string) *Plan {
	return &Plan{path}
}

func (c *Plan) Write(analysis *analyser.Analysis) error {
	file := os.Stdout
	if !isStdOut(c.path) {
		f, err := os.OpenFile(c.path, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0600)
		if err != nil {
			return err
		}
		defer f.Close()
		file = f
	}
	output := plan{FormatVersion: FormatVersion}
	output.PlannedValues.RootModule = addPlannedValues(analysis)
	output.ResourceChanges = addResourceChanges(analysis)
	jsonPlan, err := json.MarshalIndent(output, "", "\t")
	if err != nil {
		return err
	}
	if _, err := file.Write(jsonPlan); err != nil {
		return err
	}
	return nil
}

func addPlannedValues(analysis *analyser.Analysis) module {
	managedRsc := listRsc(analysis.Managed())
	unmanagedRsc := listRsc(analysis.Unmanaged())
	return module{
		Resources: append(managedRsc, unmanagedRsc...),
	}
}

func listRsc(resources []*resource.Resource) []rsc {
	var ret []rsc
	for _, res := range resources {
		r := rsc{
			Address:         fmt.Sprintf("%s.%s", res.ResourceType(), res.ResourceId()),
			Type:            res.ResourceType(),
			Name:            res.ResourceId(),
			AttributeValues: *res.Attributes(),
		}
		ret = append(ret, r)
	}
	return ret
}

func addResourceChanges(analysis *analyser.Analysis) []rscChange {
	managedRsc := listRscChange(analysis.Managed(), "no-op")
	unmanagedRsc := listRscChange(analysis.Unmanaged(), "create")
	return append(managedRsc, unmanagedRsc...)
}

func listRscChange(resources []*resource.Resource, action string) []rscChange {
	var ret []rscChange
	for _, res := range resources {
		r := rscChange{
			Address: fmt.Sprintf("%s.%s", res.ResourceType(), res.ResourceId()),
			Type:    res.ResourceType(),
			Name:    res.ResourceId(),
			Change: change{
				Actions: []string{action},
				After:   *res.Attributes(),
			},
		}
		if action == "no-op" {
			r.Change.Before = *res.Attributes()
		}
		ret = append(ret, r)

	}
	return ret
}
