// 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 (
	"strings"
	"testing"

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

const unformattedProgram = `
-- main.go --
package main
import "fmt"
func main(  ) {
	fmt.Println("Hello World.")
}
-- main.go.golden --
package main

import "fmt"

func main() {
	fmt.Println("Hello World.")
}
`

func TestFormatting(t *testing.T) {
	Run(t, unformattedProgram, func(t *testing.T, env *Env) {
		env.OpenFile("main.go")
		env.FormatBuffer("main.go")
		got := env.BufferText("main.go")
		want := env.ReadWorkspaceFile("main.go.golden")
		if got != want {
			t.Errorf("unexpected formatting result:\n%s", compare.Text(want, got))
		}
	})
}

// Tests golang/go#36824.
func TestFormattingOneLine36824(t *testing.T) {
	const onelineProgram = `
-- a.go --
package main; func f() {}

-- a.go.formatted --
package main

func f() {}
`
	Run(t, onelineProgram, func(t *testing.T, env *Env) {
		env.OpenFile("a.go")
		env.FormatBuffer("a.go")
		got := env.BufferText("a.go")
		want := env.ReadWorkspaceFile("a.go.formatted")
		if got != want {
			t.Errorf("unexpected formatting result:\n%s", compare.Text(want, got))
		}
	})
}

// Tests golang/go#36824.
func TestFormattingOneLineImports36824(t *testing.T) {
	const onelineProgramA = `
-- a.go --
package x; func f() {fmt.Println()}

-- a.go.imported --
package x

import "fmt"

func f() { fmt.Println() }
`
	Run(t, onelineProgramA, func(t *testing.T, env *Env) {
		env.OpenFile("a.go")
		env.OrganizeImports("a.go")
		got := env.BufferText("a.go")
		want := env.ReadWorkspaceFile("a.go.imported")
		if got != want {
			t.Errorf("unexpected formatting result:\n%s", compare.Text(want, got))
		}
	})
}

func TestFormattingOneLineRmImports36824(t *testing.T) {
	const onelineProgramB = `
-- a.go --
package x; import "os"; func f() {}

-- a.go.imported --
package x

func f() {}
`
	Run(t, onelineProgramB, func(t *testing.T, env *Env) {
		env.OpenFile("a.go")
		env.OrganizeImports("a.go")
		got := env.BufferText("a.go")
		want := env.ReadWorkspaceFile("a.go.imported")
		if got != want {
			t.Errorf("unexpected formatting result:\n%s", compare.Text(want, got))
		}
	})
}

const disorganizedProgram = `
-- main.go --
package main

import (
	"fmt"
	"errors"
)
func main(  ) {
	fmt.Println(errors.New("bad"))
}
-- main.go.organized --
package main

import (
	"errors"
	"fmt"
)
func main(  ) {
	fmt.Println(errors.New("bad"))
}
-- main.go.formatted --
package main

import (
	"errors"
	"fmt"
)

func main() {
	fmt.Println(errors.New("bad"))
}
`

func TestOrganizeImports(t *testing.T) {
	Run(t, disorganizedProgram, func(t *testing.T, env *Env) {
		env.OpenFile("main.go")
		env.OrganizeImports("main.go")
		got := env.BufferText("main.go")
		want := env.ReadWorkspaceFile("main.go.organized")
		if got != want {
			t.Errorf("unexpected formatting result:\n%s", compare.Text(want, got))
		}
	})
}

func TestFormattingOnSave(t *testing.T) {
	Run(t, disorganizedProgram, func(t *testing.T, env *Env) {
		env.OpenFile("main.go")
		env.SaveBuffer("main.go")
		got := env.BufferText("main.go")
		want := env.ReadWorkspaceFile("main.go.formatted")
		if got != want {
			t.Errorf("unexpected formatting result:\n%s", compare.Text(want, got))
		}
	})
}

// Tests various possibilities for comments in files with CRLF line endings.
// Import organization in these files has historically been a source of bugs.
func TestCRLFLineEndings(t *testing.T) {
	for _, tt := range []struct {
		issue, input, want string
	}{
		{
			issue: "41057",
			want: `package main

/*
Hi description
*/
func Hi() {
}
`,
		},
		{
			issue: "42646",
			want: `package main

import (
	"fmt"
)

/*
func upload(c echo.Context) error {
	if err := r.ParseForm(); err != nil {
		fmt.Fprintf(w, "ParseForm() err: %v", err)
		return
	}
	fmt.Fprintf(w, "POST request successful")
	path_ver := r.FormValue("path_ver")
	ukclin_ver := r.FormValue("ukclin_ver")

	fmt.Fprintf(w, "Name = %s\n", path_ver)
	fmt.Fprintf(w, "Address = %s\n", ukclin_ver)
}
*/

func main() {
	const server_port = 8080
	fmt.Printf("port: %d\n", server_port)
}
`,
		},
		{
			issue: "42923",
			want: `package main

// Line 1.
// aa
type Tree struct {
	arr []string
}
`,
		},
		{
			issue: "47200",
			input: `package main

import "fmt"

func main() {
	math.Sqrt(9)
	fmt.Println("hello")
}
`,
			want: `package main

import (
	"fmt"
	"math"
)

func main() {
	math.Sqrt(9)
	fmt.Println("hello")
}
`,
		},
	} {
		t.Run(tt.issue, func(t *testing.T) {
			Run(t, "-- main.go --", func(t *testing.T, env *Env) {
				input := tt.input
				if input == "" {
					input = tt.want
				}
				crlf := strings.ReplaceAll(input, "\n", "\r\n")
				env.CreateBuffer("main.go", crlf)
				env.Await(env.DoneWithOpen())
				env.OrganizeImports("main.go")
				got := env.BufferText("main.go")
				got = strings.ReplaceAll(got, "\r\n", "\n") // convert everything to LF for simplicity
				if tt.want != got {
					t.Errorf("unexpected content after save:\n%s", compare.Text(tt.want, got))
				}
			})
		})
	}
}

func TestFormattingOfGeneratedFile_Issue49555(t *testing.T) {
	const input = `
-- main.go --
// Code generated by generator.go. DO NOT EDIT.

package main

import "fmt"

func main() {




	fmt.Print("hello")
}
`

	Run(t, input, func(t *testing.T, env *Env) {
		wantErrSuffix := "file is generated"

		env.OpenFile("main.go")
		err := env.Editor.FormatBuffer(env.Ctx, "main.go")
		if err == nil {
			t.Fatal("expected error, got nil")
		}
		// Check only the suffix because an error contains a dynamic path to main.go
		if !strings.HasSuffix(err.Error(), wantErrSuffix) {
			t.Fatalf("unexpected error %q, want suffix %q", err.Error(), wantErrSuffix)
		}
	})
}

func TestGofumptFormatting(t *testing.T) {
	testenv.NeedsGo1Point(t, 20) // gofumpt requires go 1.20+
	// Exercise some gofumpt formatting rules:
	//  - No empty lines following an assignment operator
	//  - Octal integer literals should use the 0o prefix on modules using Go
	//    1.13 and later. Requires LangVersion to be correctly resolved.
	//  - std imports must be in a separate group at the top. Requires ModulePath
	//    to be correctly resolved.
	const input = `
-- go.mod --
module foo

go 1.17
-- foo.go --
package foo

import (
	"foo/bar"
	"fmt"
)

const perm = 0755

func foo() {
	foo :=
		"bar"
	fmt.Println(foo, bar.Bar)
}
-- foo.go.formatted --
package foo

import (
	"fmt"

	"foo/bar"
)

const perm = 0o755

func foo() {
	foo := "bar"
	fmt.Println(foo, bar.Bar)
}
-- bar/bar.go --
package bar

const Bar = 42
`

	WithOptions(
		Settings{
			"gofumpt": true,
		},
	).Run(t, input, func(t *testing.T, env *Env) {
		env.OpenFile("foo.go")
		env.FormatBuffer("foo.go")
		got := env.BufferText("foo.go")
		want := env.ReadWorkspaceFile("foo.go.formatted")
		if got != want {
			t.Errorf("unexpected formatting result:\n%s", compare.Text(want, got))
		}
	})
}

func TestGofumpt_Issue61692(t *testing.T) {
	testenv.NeedsGo1Point(t, 21)

	const input = `
-- go.mod --
module foo

go 1.21rc3
-- foo.go --
package foo

func _() {
	foo :=
		"bar"
}
`

	WithOptions(
		Settings{
			"gofumpt": true,
		},
	).Run(t, input, func(t *testing.T, env *Env) {
		env.OpenFile("foo.go")
		env.FormatBuffer("foo.go") // golang/go#61692: must not panic
	})
}
