// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package misc

import (
	"testing"

	"golang.org/x/tools/gopls/internal/settings"
	"golang.org/x/tools/gopls/internal/test/compare"
	. "golang.org/x/tools/gopls/internal/test/integration"

	"golang.org/x/tools/gopls/internal/protocol"
)

// A basic test for fillstruct, now that it uses a command and supports resolve edits.
func TestFillStruct(t *testing.T) {
	tc := []struct {
		name         string
		capabilities string
		wantCommand  bool
	}{
		{"default", "{}", false},
		{"no data support", `{"textDocument": {"codeAction": {"dataSupport": false, "resolveSupport": {"properties": ["edit"]}}}}`, true},
		{"no resolve support", `{"textDocument": {"codeAction": {"dataSupport": true, "resolveSupport": {"properties": []}}}}`, true},
		{"data and resolve support", `{"textDocument": {"codeAction": {"dataSupport": true, "resolveSupport": {"properties": ["edit"]}}}}`, false},
	}

	const basic = `
-- go.mod --
module mod.com

go 1.14
-- main.go --
package main

type Info struct {
	WordCounts map[string]int
	Words []string
}

func Foo() {
	_ = Info{}
}
`

	for _, tt := range tc {
		t.Run(tt.name, func(t *testing.T) {
			runner := WithOptions(CapabilitiesJSON([]byte(tt.capabilities)))

			runner.Run(t, basic, func(t *testing.T, env *Env) {
				env.OpenFile("main.go")
				fixes, err := env.Editor.CodeActions(env.Ctx, env.RegexpSearch("main.go", "Info{}"), nil, settings.RefactorRewriteFillStruct)
				if err != nil {
					t.Fatal(err)
				}

				if len(fixes) != 1 {
					t.Fatalf("expected 1 code action, got %v", len(fixes))
				}
				if tt.wantCommand {
					if fixes[0].Command == nil || fixes[0].Data != nil {
						t.Errorf("expected code action to have command not data, got %v", fixes[0])
					}
				} else {
					if fixes[0].Command != nil || fixes[0].Data == nil {
						t.Errorf("expected code action to have command not data, got %v", fixes[0])
					}
				}

				// Apply the code action (handles resolving the code action), and check that the result is correct.
				if err := env.Editor.RefactorRewrite(env.Ctx, env.RegexpSearch("main.go", "Info{}")); err != nil {
					t.Fatal(err)
				}
				want := `package main

type Info struct {
	WordCounts map[string]int
	Words []string
}

func Foo() {
	_ = Info{
		WordCounts: map[string]int{},
		Words:      []string{},
	}
}
`
				if got := env.BufferText("main.go"); got != want {
					t.Fatalf("TestFillStruct failed:\n%s", compare.Text(want, got))
				}
			})
		})
	}
}

func TestFillReturns(t *testing.T) {
	const files = `
-- go.mod --
module mod.com

go 1.12
-- main.go --
package main

func Foo() error {
	return
}
`
	Run(t, files, func(t *testing.T, env *Env) {
		env.OpenFile("main.go")
		var d protocol.PublishDiagnosticsParams
		env.AfterChange(
			// The error message here changed in 1.18; "return values" covers both forms.
			Diagnostics(env.AtRegexp("main.go", `return`), WithMessage("return values")),
			ReadDiagnostics("main.go", &d),
		)
		var quickFixes []*protocol.CodeAction
		for _, act := range env.CodeActionForFile("main.go", d.Diagnostics) {
			if act.Kind == protocol.QuickFix {
				act := act // remove in go1.22
				quickFixes = append(quickFixes, &act)
			}
		}
		if len(quickFixes) != 1 {
			t.Fatalf("expected 1 quick fix, got %d:\n%v", len(quickFixes), quickFixes)
		}
		env.ApplyQuickFixes("main.go", d.Diagnostics)
		env.AfterChange(NoDiagnostics(ForFile("main.go")))
	})
}

func TestUnusedParameter_Issue63755(t *testing.T) {
	// This test verifies the fix for #63755, where codeActions panicked on parameters
	// of functions with no function body.

	// We should not detect parameters as unused for external functions.

	const files = `
-- go.mod --
module unused.mod

go 1.18

-- external.go --
package external

func External(z int)

func _() {
	External(1)
}
	`
	Run(t, files, func(t *testing.T, env *Env) {
		env.OpenFile("external.go")
		_, err := env.Editor.CodeAction(env.Ctx, env.RegexpSearch("external.go", "z"), nil, protocol.CodeActionUnknownTrigger)
		if err != nil {
			t.Fatal(err)
		}
		// yay, no panic
	})
}
