package local

import (
	"context"
	"errors"
	"log"

	"github.com/hashicorp/errwrap"
	"github.com/hashicorp/terraform/backend"
	"github.com/hashicorp/terraform/command/clistate"
	"github.com/hashicorp/terraform/command/format"
	"github.com/hashicorp/terraform/state"
	"github.com/hashicorp/terraform/terraform"
	"github.com/hashicorp/terraform/tfdiags"
)

// backend.Local implementation.
func (b *Local) Context(op *backend.Operation) (*terraform.Context, state.State, error) {
	// Make sure the type is invalid. We use this as a way to know not
	// to ask for input/validate.
	op.Type = backend.OperationTypeInvalid

	if op.LockState {
		op.StateLocker = clistate.NewLocker(context.Background(), op.StateLockTimeout, b.CLI, b.Colorize())
	} else {
		op.StateLocker = clistate.NewNoopLocker()
	}

	return b.context(op)
}

func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State, error) {
	// Get the state.
	s, err := b.State(op.Workspace)
	if err != nil {
		return nil, nil, errwrap.Wrapf("Error loading state: {{err}}", err)
	}

	if err := op.StateLocker.Lock(s, op.Type.String()); err != nil {
		return nil, nil, errwrap.Wrapf("Error locking state: {{err}}", err)
	}

	if err := s.RefreshState(); err != nil {
		return nil, nil, errwrap.Wrapf("Error loading state: {{err}}", err)
	}

	// Initialize our context options
	var opts terraform.ContextOpts
	if v := b.ContextOpts; v != nil {
		opts = *v
	}

	// Copy set options from the operation
	opts.Destroy = op.Destroy
	opts.Module = op.Module
	opts.Targets = op.Targets
	opts.UIInput = op.UIIn

	// Load our state
	// By the time we get here, the backend creation code in "command" took
	// care of making s.State() return a state compatible with our plan,
	// if any, so we can safely pass this value in both the plan context
	// and new context cases below.
	opts.State = s.State()

	// Build the context
	var tfCtx *terraform.Context
	if op.Plan != nil {
		tfCtx, err = op.Plan.Context(&opts)
	} else {
		tfCtx, err = terraform.NewContext(&opts)
	}

	// any errors resolving plugins returns this
	if rpe, ok := err.(*terraform.ResourceProviderError); ok {
		b.pluginInitRequired(rpe)
		// we wrote the full UI error here, so return a generic error for flow
		// control in the command.
		return nil, nil, errors.New("error satisfying plugin requirements")
	}

	if err != nil {
		return nil, nil, err
	}

	// If we have an operation, then we automatically do the input/validate
	// here since every option requires this.
	if op.Type != backend.OperationTypeInvalid {
		// If input asking is enabled, then do that
		if op.Plan == nil && b.OpInput {
			mode := terraform.InputModeProvider
			mode |= terraform.InputModeVar
			mode |= terraform.InputModeVarUnset

			if err := tfCtx.Input(mode); err != nil {
				return nil, nil, errwrap.Wrapf("Error asking for user input: {{err}}", err)
			}
		}

		// If validation is enabled, validate
		if b.OpValidation {
			diags := tfCtx.Validate()
			if len(diags) > 0 {
				if diags.HasErrors() {
					// If there are warnings _and_ errors then we'll take this
					// path and return them all together in this error.
					return nil, nil, diags.Err()
				}

				// For now we can't propagate warnings any further without
				// printing them directly to the UI, so we'll need to
				// format them here ourselves.
				for _, diag := range diags {
					if diag.Severity() != tfdiags.Warning {
						continue
					}
					if b.CLI != nil {
						b.CLI.Warn(format.Diagnostic(diag, b.Colorize(), 72))
					} else {
						desc := diag.Description()
						log.Printf("[WARN] backend/local: %s", desc.Summary)
					}
				}

				// Make a newline before continuing
				b.CLI.Output("")
			}
		}
	}

	return tfCtx, s, nil
}

const validateWarnHeader = `
There are warnings related to your configuration. If no errors occurred,
Terraform will continue despite these warnings. It is a good idea to resolve
these warnings in the near future.

Warnings:
`
