package agent

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	"code.justin.tv/systems/sandstorm/manager"
	log "github.com/Sirupsen/logrus"
)

// State represents current status of templates and their resulting secrets
type State struct {
	LastUpdate           []*manager.Secret
	LastSuccessfulUpdate []*manager.Secret
	Templates            []*Template
	RecentError          RecentError
	BlockedSecrets       map[string]string
}

// StateFile represents a snapshot of the Agent for a given point in time. Use
// `newStateFile()` to create.
type StateFile struct {
	Templates  map[string]*Template
	LastUpdate int64
}

// NewFileState prepares a snapshot of templates
// and secret for file output
func newStateFile(templates []*Template) *StateFile {
	state := &StateFile{
		Templates: map[string]*Template{},
	}

	// manually add templates, since we don't want to directly copy pointers
	// across as we'll be removing some values
	state.AddTemplates(templates)
	return state
}

// ParseStateFile reads in an existing state from file
func parseStateFile(path string) (*StateFile, error) {
	err := errIfNotAbs(path)
	if err != nil {
		return nil, InvalidStateFile{err.Error()}
	}

	byteState, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, InvalidStateFile{err.Error()}
	}

	stateFile := &StateFile{}
	err = json.Unmarshal(byteState, stateFile)
	if err != nil {
		return nil, InvalidStateFile{err.Error()}
	}

	// assume that all templates are clean if they are coming from
	// a statefile
	for _, template := range stateFile.Templates {
		template.Dirty = false
		if template.CommandStatus == nil {
			template.CommandStatus = &CommandStatus{}
		}
		template.CommandStatus.Status = statusStartup
	}

	log.Info("State File Parsed Successfully")
	return stateFile, nil
}

// AddTemplates adds templates to the FileState while ignoring any sensitive
// information the templates might hold while in memory
func (f *StateFile) AddTemplates(templates []*Template) {

	// we dereference the pointer values and then copy so that when we
	for _, template := range templates {
		copy := copyTemplate(template)
		for _, secret := range copy.Secrets {
			secret.Plaintext = []byte{}
		}
		f.Templates[copy.Source] = copy
	}
}

// ToFile handles writing the FileState to the given path
func (f *StateFile) ToFile(path string) error {

	f.LastUpdate = time.Now().Unix()

	json, err := json.MarshalIndent(f, "", " ")
	if err != nil {
		return err
	}

	file, err := os.Create(path)
	if err != nil {
		return err
	}
	defer func() {
		err := file.Close()
		if err != nil {
			log.Error(fmt.Errorf("Error closing state file: %s", err.Error()))
		}
	}()

	err = os.Chmod(path, StateFilePermissions)
	if err != nil {
		return err
	}

	_, err = file.WriteString(string(json))
	if err != nil {
		return err
	}

	return file.Sync()
}
