package swift

import (
	"fmt"
	"strings"

	"github.com/hashicorp/terraform/backend"
	"github.com/hashicorp/terraform/states"
	"github.com/hashicorp/terraform/states/remote"
	"github.com/hashicorp/terraform/states/statemgr"
)

const (
	objectEnvPrefix = "env-"
	delimiter       = "/"
)

func (b *Backend) Workspaces() ([]string, error) {
	client := &RemoteClient{
		client:           b.client,
		container:        b.container,
		archive:          b.archive,
		archiveContainer: b.archiveContainer,
		expireSecs:       b.expireSecs,
		lockState:        b.lock,
	}

	// List our container objects
	objectNames, err := client.ListObjectsNames(objectEnvPrefix, delimiter)

	if err != nil {
		return nil, err
	}

	// Find the envs, we use a map since we can get duplicates with
	// path suffixes.
	envs := map[string]struct{}{}
	for _, object := range objectNames {
		object = strings.TrimPrefix(object, objectEnvPrefix)
		object = strings.TrimSuffix(object, delimiter)

		// Ignore objects that still contain a "/"
		// as we dont store states in subdirectories
		if idx := strings.Index(object, delimiter); idx >= 0 {
			continue
		}

		// swift is eventually consistent, thus a deleted object may
		// be listed in objectList. To ensure consistency, we query
		// each object  with a "newest" arg set to true
		payload, err := client.get(b.objectName(object))
		if err != nil {
			return nil, err
		}
		if payload == nil {
			// object doesn't exist anymore. skipping.
			continue
		}

		envs[object] = struct{}{}
	}

	result := make([]string, 1, len(envs)+1)
	result[0] = backend.DefaultStateName

	for k := range envs {
		result = append(result, k)
	}

	return result, nil
}

func (b *Backend) DeleteWorkspace(name string) error {
	if name == backend.DefaultStateName || name == "" {
		return fmt.Errorf("can't delete default state")
	}

	client := &RemoteClient{
		client:           b.client,
		container:        b.container,
		archive:          b.archive,
		archiveContainer: b.archiveContainer,
		expireSecs:       b.expireSecs,
		objectName:       b.objectName(name),
		lockState:        b.lock,
	}

	// Delete our object
	err := client.Delete()

	return err
}

func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
	if name == "" {
		return nil, fmt.Errorf("missing state name")
	}

	client := &RemoteClient{
		client:           b.client,
		container:        b.container,
		archive:          b.archive,
		archiveContainer: b.archiveContainer,
		expireSecs:       b.expireSecs,
		objectName:       b.objectName(name),
		lockState:        b.lock,
	}

	var stateMgr statemgr.Full = &remote.State{Client: client}

	// If we're not locking, disable it
	if !b.lock {
		stateMgr = &statemgr.LockDisabled{Inner: stateMgr}
	}

	// Check to see if this state already exists.
	// If we're trying to force-unlock a state, we can't take the lock before
	// fetching the state. If the state doesn't exist, we have to assume this
	// is a normal create operation, and take the lock at that point.
	//
	// If we need to force-unlock, but for some reason the state no longer
	// exists, the user will have to use openstack tools to manually fix the
	// situation.
	existing, err := b.Workspaces()
	if err != nil {
		return nil, err
	}

	exists := false
	for _, s := range existing {
		if s == name {
			exists = true
			break
		}
	}

	// We need to create the object so it's listed by States.
	if !exists {
		// the default state always exists
		if name == backend.DefaultStateName {
			return stateMgr, nil
		}

		// Grab a lock, we use this to write an empty state if one doesn't
		// exist already. We have to write an empty state as a sentinel value
		// so States() knows it exists.
		lockInfo := statemgr.NewLockInfo()
		lockInfo.Operation = "init"
		lockId, err := stateMgr.Lock(lockInfo)
		if err != nil {
			return nil, fmt.Errorf("failed to lock state in Swift: %s", err)
		}

		// Local helper function so we can call it multiple places
		lockUnlock := func(parent error) error {
			if err := stateMgr.Unlock(lockId); err != nil {
				return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
			}

			return parent
		}

		// Grab the value
		if err := stateMgr.RefreshState(); err != nil {
			err = lockUnlock(err)
			return nil, err
		}

		// If we have no state, we have to create an empty state
		if v := stateMgr.State(); v == nil {
			if err := stateMgr.WriteState(states.NewState()); err != nil {
				err = lockUnlock(err)
				return nil, err
			}
			if err := stateMgr.PersistState(); err != nil {
				err = lockUnlock(err)
				return nil, err
			}
		}

		// Unlock, the state should now be initialized
		if err := lockUnlock(nil); err != nil {
			return nil, err
		}
	}

	return stateMgr, nil
}

func (b *Backend) objectName(name string) string {
	if name != backend.DefaultStateName {
		name = fmt.Sprintf("%s%s/%s", objectEnvPrefix, name, b.stateName)
	} else {
		name = b.stateName
	}

	return name
}

const errStateUnlock = `
Error unlocking Swift state. Lock ID: %s

Error: %s

You may have to force-unlock this state in order to use it again.
The Swift backend acquires a lock during initialization to ensure
the minimum required keys are prepared.
`
