// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1

package remote

import (
	"github.com/hashicorp/terraform/internal/providers"
	"github.com/hashicorp/terraform/internal/states/statemgr"
	"github.com/hashicorp/terraform/internal/tfdiags"
)

// NewRemoteGRPC returns a remote state manager (remote.State) containing
// an implementation of remote.Client that allows Terraform to interact with
// a provider implementing pluggable state storage.
//
// The remote.Client implementation's methods invoke the provider's RPC
// methods to perform tasks like reading in state, locking, etc.
//
// NewRemoteGRPC requires these arguments to create the remote.Client:
// 1) the provider interface, needed to call gRPC methods
// 2) the name of the state storage implementation in the provider
// 3) the name of the state/the active workspace
func NewRemoteGRPC(provider providers.Interface, typeName string, stateId string) statemgr.Full {
	mgr := &State{
		Client: &grpcClient{
			provider: provider,
			typeName: typeName,
			stateId:  stateId,
		},
	}
	return mgr
}

var (
	_ Client       = &grpcClient{}
	_ ClientLocker = &grpcClient{}
)

// grpcClient acts like a client to enable the State state manager
// to communicate with a provider that implements pluggable state
// storage via gRPC.
//
// The calling code needs to provide information about the store's name
// and the name of the state (i.e. CE workspace) to use, as these are
// arguments required in gRPC requests.
type grpcClient struct {
	provider providers.Interface
	typeName string // the state storage implementation's name
	stateId  string
}

// Get invokes the ReadStateBytes gRPC method in the plugin protocol
// and returns a copy of the downloaded state data.
//
// Implementation of remote.Client
func (g *grpcClient) Get() (*Payload, tfdiags.Diagnostics) {
	req := providers.ReadStateBytesRequest{
		TypeName: g.typeName,
		StateId:  g.stateId,
	}
	resp := g.provider.ReadStateBytes(req)

	if len(resp.Bytes) == 0 {
		// No state to return
		return nil, resp.Diagnostics
	}

	// TODO: Remove or replace use of MD5?
	// The MD5 value here is never used.
	payload := &Payload{
		Data: resp.Bytes,
		MD5:  []byte{}, // empty, as this is unused downstream
	}
	return payload, resp.Diagnostics
}

// Put invokes the WriteStateBytes gRPC method in the plugin protocol
// and to transfer state data to the remote location.
//
// Implementation of remote.Client
func (g *grpcClient) Put(state []byte) tfdiags.Diagnostics {
	if len(state) == 0 {
		var diags tfdiags.Diagnostics
		return diags.Append(tfdiags.Sourceless(
			tfdiags.Error,
			"Refusing to write empty remote state snapshot",
			"Terraform produced an empty state file and will not upload it to remote storage. This indicates a bug in Terraform; please report it.",
		))
	}

	req := providers.WriteStateBytesRequest{
		TypeName: g.typeName,
		StateId:  g.stateId,
		Bytes:    state,
	}
	resp := g.provider.WriteStateBytes(req)

	return resp.Diagnostics
}

// Delete invokes the DeleteState gRPC method in the plugin protocol
// to delete a named state in the remote location.
//
// NOTE: this is included to fulfil an interface, but deletion of
// workspaces is actually achieved through the backend.Backend
// interface's DeleteWorkspace method.
//
// Implementation of remote.Client
func (g *grpcClient) Delete() tfdiags.Diagnostics {
	req := providers.DeleteStateRequest{
		TypeName: g.typeName,
		StateId:  g.stateId,
	}
	resp := g.provider.DeleteState(req)
	return resp.Diagnostics
}

// Lock invokes the LockState gRPC method in the plugin protocol
// to lock a named state in the remote location.
//
// Implementation of remote.Client
func (g *grpcClient) Lock(lock *statemgr.LockInfo) (string, error) {
	req := providers.LockStateRequest{
		TypeName:  g.typeName,
		StateId:   g.stateId,
		Operation: lock.Operation,
	}
	resp := g.provider.LockState(req)
	return resp.LockId, resp.Diagnostics.Err()
}

// Unlock invokes the UnlockState gRPC method in the plugin protocol
// to release a named lock on a specific state in the remote location.
//
// Implementation of remote.Client
func (g *grpcClient) Unlock(id string) error {
	req := providers.UnlockStateRequest{
		TypeName: g.typeName,
		StateId:  g.stateId,
		LockId:   id,
	}
	resp := g.provider.UnlockState(req)
	return resp.Diagnostics.Err()
}
