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

package e2etest

import (
	"context"
	"encoding/json"
	"io"
	"path/filepath"
	"regexp"
	"strings"
	"testing"

	"github.com/hashicorp/go-hclog"
	"github.com/hashicorp/go-plugin"
	"github.com/hashicorp/terraform/internal/e2e"
	"github.com/hashicorp/terraform/internal/grpcwrap"
	tfplugin5 "github.com/hashicorp/terraform/internal/plugin"
	tfplugin "github.com/hashicorp/terraform/internal/plugin6"
	simple "github.com/hashicorp/terraform/internal/provider-simple-v6"
	proto5 "github.com/hashicorp/terraform/internal/tfplugin5"
	proto "github.com/hashicorp/terraform/internal/tfplugin6"
)

func TestUnmanagedQuery(t *testing.T) {
	if !canRunGoBuild {
		// We're running in a separate-build-then-run context, so we can't
		// currently execute this test which depends on being able to build
		// new executable at runtime.
		//
		// (See the comment on canRunGoBuild's declaration for more information.)
		t.Skip("can't run without building a new provider executable")
	}

	t.Parallel()

	tests := []struct {
		name            string
		protocolVersion int
	}{
		{
			name:            "proto6",
			protocolVersion: 6,
		},
		{
			name:            "proto5",
			protocolVersion: 5,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			fixturePath := filepath.Join("testdata", "query-provider")
			tf := e2e.NewBinary(t, terraformBin, fixturePath)

			reattachCh := make(chan *plugin.ReattachConfig)
			closeCh := make(chan struct{})

			var provider interface {
				ListResourceCalled() bool
			}
			var versionedPlugins map[int]plugin.PluginSet

			// Configure provider and plugins based on protocol version
			if tc.protocolVersion == 6 {
				provider6 := &providerServer{
					ProviderServer: grpcwrap.Provider6(simple.Provider()),
				}
				provider = provider6
				versionedPlugins = map[int]plugin.PluginSet{
					6: {
						"provider": &tfplugin.GRPCProviderPlugin{
							GRPCProvider: func() proto.ProviderServer {
								return provider6
							},
						},
					},
				}
			} else {
				provider5 := &providerServer5{
					ProviderServer: grpcwrap.Provider(simple.Provider()),
				}
				provider = provider5
				versionedPlugins = map[int]plugin.PluginSet{
					5: {
						"provider": &tfplugin5.GRPCProviderPlugin{
							GRPCProvider: func() proto5.ProviderServer {
								return provider5
							},
						},
					},
				}
			}

			ctx, cancel := context.WithCancel(context.Background())
			defer cancel()
			go plugin.Serve(&plugin.ServeConfig{
				Logger: hclog.New(&hclog.LoggerOptions{
					Name:   "plugintest",
					Level:  hclog.Trace,
					Output: io.Discard,
				}),
				Test: &plugin.ServeTestConfig{
					Context:          ctx,
					ReattachConfigCh: reattachCh,
					CloseCh:          closeCh,
				},
				GRPCServer:       plugin.DefaultGRPCServer,
				VersionedPlugins: versionedPlugins,
			})
			config := <-reattachCh
			if config == nil {
				t.Fatalf("no reattach config received")
			}
			reattachStr, err := json.Marshal(map[string]reattachConfig{
				"hashicorp/test": {
					Protocol:        string(config.Protocol),
					ProtocolVersion: tc.protocolVersion,
					Pid:             config.Pid,
					Test:            true,
					Addr: reattachConfigAddr{
						Network: config.Addr.Network(),
						String:  config.Addr.String(),
					},
				},
			})
			if err != nil {
				t.Fatal(err)
			}

			tf.AddEnv("TF_REATTACH_PROVIDERS=" + string(reattachStr))

			//// INIT
			stdout, stderr, err := tf.Run("init")
			if err != nil {
				t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr)
			}

			// Make sure we didn't download the binary
			if strings.Contains(stdout, "Installing hashicorp/test v") {
				t.Errorf("test provider download message is present in init output:\n%s", stdout)
			}
			if tf.FileExists(filepath.Join(".terraform", "plugins", "registry.terraform.io", "hashicorp", "test")) {
				t.Errorf("test provider binary found in .terraform dir")
			}

			//// QUERY
			stdout, stderr, err = tf.Run("query")
			if err != nil {
				t.Fatalf("unexpected query error: %s\nstderr:\n%s", err, stderr)
			}

			if !provider.ListResourceCalled() {
				t.Error("ListResource not called on un-managed provider")
			}

			// The output should contain the expected resource data. (using regex so that the number of whitespace characters doesn't matter)
			regex := regexp.MustCompile(`(?m)^list\.simple_resource\.test\s+id=static_id\s+static_display_name$`)
			if !regex.MatchString(stdout) {
				t.Errorf("expected resource data not found in output:\n%s", stdout)
			}

			cancel()
			<-closeCh
		})
	}
}
