package state

import (
	"context"
	"fmt"
	"os"
	"os/user"
	"time"

	uuid "github.com/hashicorp/go-uuid"

	"github.com/hashicorp/terraform/states/statemgr"
	"github.com/hashicorp/terraform/version"
)

// State is a deprecated alias for statemgr.Full
type State = statemgr.Full

// StateReader is a deprecated alias for statemgr.Reader
type StateReader = statemgr.Reader

// StateWriter is a deprecated alias for statemgr.Writer
type StateWriter = statemgr.Writer

// StateRefresher is a deprecated alias for statemgr.Refresher
type StateRefresher = statemgr.Refresher

// StatePersister is a deprecated alias for statemgr.Persister
type StatePersister = statemgr.Persister

// Locker is a deprecated alias for statemgr.Locker
type Locker = statemgr.Locker

// test hook to verify that LockWithContext has attempted a lock
var postLockHook func()

// Lock the state, using the provided context for timeout and cancellation.
// This backs off slightly to an upper limit.
func LockWithContext(ctx context.Context, s State, info *LockInfo) (string, error) {
	delay := time.Second
	maxDelay := 16 * time.Second
	for {
		id, err := s.Lock(info)
		if err == nil {
			return id, nil
		}

		le, ok := err.(*LockError)
		if !ok {
			// not a lock error, so we can't retry
			return "", err
		}

		if le == nil || le.Info == nil || le.Info.ID == "" {
			// If we dont' have a complete LockError, there's something wrong with the lock
			return "", err
		}

		if postLockHook != nil {
			postLockHook()
		}

		// there's an existing lock, wait and try again
		select {
		case <-ctx.Done():
			// return the last lock error with the info
			return "", err
		case <-time.After(delay):
			if delay < maxDelay {
				delay *= 2
			}
		}
	}
}

// Generate a LockInfo structure, populating the required fields.
func NewLockInfo() *LockInfo {
	id, err := uuid.GenerateUUID()
	if err != nil {
		// this of course shouldn't happen
		panic(err)
	}

	// don't error out on user and hostname, as we don't require them
	userName := ""
	if userInfo, err := user.Current(); err == nil {
		userName = userInfo.Username
	}
	host, _ := os.Hostname()

	info := &LockInfo{
		ID:      id,
		Who:     fmt.Sprintf("%s@%s", userName, host),
		Version: version.Version,
		Created: time.Now().UTC(),
	}
	return info
}

// LockInfo is a deprecated lias for statemgr.LockInfo
type LockInfo = statemgr.LockInfo

// LockError is a deprecated alias for statemgr.LockError
type LockError = statemgr.LockError
