//go:build go1.16
// +build go1.16

package toml_test

import (
	"errors"
	"fmt"
	"io/fs"
	"math"
	"strings"
	"testing"
	"time"

	"github.com/BurntSushi/toml"
	tomltest "github.com/BurntSushi/toml/internal/toml-test"
)

func TestErrorPosition(t *testing.T) {
	// Note: take care to use leading spaces (not tabs).
	tests := []struct {
		test, err string
	}{
		{"array/missing-separator.toml", `
toml: error: expected a comma (',') or array terminator (']'), but got '2'

At line 1, column 13:

      1 | wrong = [ 1 2 3 ]
                      ^`},

		{"array/no-close-2.toml", `
toml: error: expected a comma (',') or array terminator (']'), but got end of file

At line 1, column 10:

      1 | x = [42 #
                   ^`},

		{"array/tables-2.toml", `
toml: error: Key 'fruit.variety' has already been defined.

At line 9, column 3-8:

      7 | 
      8 |   # This table conflicts with the previous table
      9 |   [fruit.variety]
             ^^^^^`},
		{"datetime/trailing-t.toml", `
toml: error: Invalid TOML Datetime: "2006-01-30T".

At line 2, column 4-15:

      1 | # Date cannot end with trailing T
      2 | d = 2006-01-30T
              ^^^^^^^^^^^`},
	}

	fsys := tomltest.EmbeddedTests()
	for _, tt := range tests {
		t.Run(tt.test, func(t *testing.T) {
			input, err := fs.ReadFile(fsys, "invalid/"+tt.test)
			if err != nil {
				t.Fatal(err)
			}

			var x interface{}
			_, err = toml.Decode(string(input), &x)
			if err == nil {
				t.Fatal("err is nil")
			}

			var pErr toml.ParseError
			if !errors.As(err, &pErr) {
				t.Errorf("err is not a ParseError: %T %[1]v", err)
			}

			tt.err = tt.err[1:] + "\n" // Remove first newline, and add trailing.
			want := pErr.ErrorWithUsage()

			if !strings.Contains(want, tt.err) {
				t.Fatalf("\nwant:\n%s\nhave:\n%s", tt.err, want)
			}
		})
	}
}

func TestParseError(t *testing.T) {
	tests := []struct {
		in        interface{}
		toml, err string
	}{
		{
			&struct{ Int int8 }{},
			"Int = 200",
			`| toml: error: 200 is out of range for int8
			 |
			 | At line 1, column 6-9:
			 |
			 |       1 | Int = 200
			 |                 ^^^
			 | Error help:
             |
			 |     This number is too large; this may be an error in the TOML, but it can also be a
			 |     bug in the program that uses too small of an integer.
             |
			 |     The maximum and minimum values are:
             |
			 |         size   │ lowest         │ highest
			 |         ───────┼────────────────┼──────────
			 |         int8   │ -128           │ 127
			 |         int16  │ -32,768        │ 32,767
			 |         int32  │ -2,147,483,648 │ 2,147,483,647
			 |         int64  │ -9.2 × 10¹⁷    │ 9.2 × 10¹⁷
			 |         uint8  │ 0              │ 255
			 |         uint16 │ 0              │ 65535
			 |         uint32 │ 0              │ 4294967295
			 |         uint64 │ 0              │ 1.8 × 10¹⁸
             |
			 |     int refers to int32 on 32-bit systems and int64 on 64-bit systems.
			`,
		},
		{
			&struct{ Int int }{},
			fmt.Sprintf("Int = %d", uint64(math.MaxInt64+1)),
			`| toml: error: 9223372036854775808 is out of range for int64
			 |
			 | At line 1, column 6-25:
			 |
			 |       1 | Int = 9223372036854775808
			 |                 ^^^^^^^^^^^^^^^^^^^
			 | Error help:
             |
			 |     This number is too large; this may be an error in the TOML, but it can also be a
			 |     bug in the program that uses too small of an integer.
             |
			 |     The maximum and minimum values are:
             |
			 |         size   │ lowest         │ highest
			 |         ───────┼────────────────┼──────────
			 |         int8   │ -128           │ 127
			 |         int16  │ -32,768        │ 32,767
			 |         int32  │ -2,147,483,648 │ 2,147,483,647
			 |         int64  │ -9.2 × 10¹⁷    │ 9.2 × 10¹⁷
			 |         uint8  │ 0              │ 255
			 |         uint16 │ 0              │ 65535
			 |         uint32 │ 0              │ 4294967295
			 |         uint64 │ 0              │ 1.8 × 10¹⁸
             |
			 |     int refers to int32 on 32-bit systems and int64 on 64-bit systems.
			`,
		},
		{
			&struct{ Float float32 }{},
			"Float = 1.1e99",
			`
            | toml: error: 1.1e+99 is out of range for float32
            |
            | At line 1, column 8-14:
            |
            |       1 | Float = 1.1e99
            |                   ^^^^^^
            | Error help:
            |
            |     This number is too large; this may be an error in the TOML, but it can also be a
            |     bug in the program that uses too small of an integer.
            |
            |     The maximum and minimum values are:
            |
            |         size   │ lowest         │ highest
            |         ───────┼────────────────┼──────────
            |         int8   │ -128           │ 127
            |         int16  │ -32,768        │ 32,767
            |         int32  │ -2,147,483,648 │ 2,147,483,647
            |         int64  │ -9.2 × 10¹⁷    │ 9.2 × 10¹⁷
            |         uint8  │ 0              │ 255
            |         uint16 │ 0              │ 65535
            |         uint32 │ 0              │ 4294967295
            |         uint64 │ 0              │ 1.8 × 10¹⁸
            |
            |     int refers to int32 on 32-bit systems and int64 on 64-bit systems.
			`,
		},

		{
			&struct{ D time.Duration }{},
			`D = "99 bottles"`,
			`
			| toml: error: invalid duration: "99 bottles"
			|
			| At line 1, column 5-15:
			|
			|       1 | D = "99 bottles"
			|                ^^^^^^^^^^
			| Error help:
			|
			|     A duration must be as "number<unit>", without any spaces. Valid units are:
			|
			|         ns         nanoseconds (billionth of a second)
			|         us, µs     microseconds (millionth of a second)
			|         ms         milliseconds (thousands of a second)
			|         s          seconds
			|         m          minutes
			|         h          hours
			|
			|     You can combine multiple units; for example "5m10s" for 5 minutes and 10
			|     seconds.
			`,
		},
	}

	prep := func(s string) string {
		lines := strings.Split(strings.TrimSpace(s), "\n")
		for i := range lines {
			if j := strings.IndexByte(lines[i], '|'); j >= 0 {
				lines[i] = lines[i][j+1:]
				lines[i] = strings.Replace(lines[i], " ", "", 1)
			}
		}
		return strings.Join(lines, "\n") + "\n"
	}

	for _, tt := range tests {
		t.Run("", func(t *testing.T) {
			_, err := toml.Decode(tt.toml, tt.in)
			if err == nil {
				t.Fatalf("err is nil; decoded: %#v", tt.in)
			}

			var pErr toml.ParseError
			if !errors.As(err, &pErr) {
				t.Fatalf("err is not a ParseError: %#v", err)
			}

			tt.err = prep(tt.err)
			have := pErr.ErrorWithUsage()

			// have = strings.ReplaceAll(have, " ", "·")
			// tt.err = strings.ReplaceAll(tt.err, " ", "·")
			if have != tt.err {
				t.Fatalf("\nwant:\n%s\nhave:\n%s", tt.err, have)
			}
		})
	}
}
