// 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 json

import (
	"archive/tar"
	"bytes"
	"errors"
	"io"
	"strings"
	"testing"

	"github.com/go-json-experiment/json/internal/jsonwire"
	"github.com/go-json-experiment/json/jsontext"
)

func TestSemanticError(t *testing.T) {
	tests := []struct {
		err  error
		want string
	}{{
		err:  &SemanticError{},
		want: `json: cannot handle`,
	}, {
		err:  &SemanticError{JSONKind: 'n'},
		want: `json: cannot handle JSON null`,
	}, {
		err:  &SemanticError{action: "unmarshal", JSONKind: 't'},
		want: `json: cannot unmarshal JSON boolean`,
	}, {
		err:  &SemanticError{action: "unmarshal", JSONKind: 'x'},
		want: `json: cannot unmarshal`, // invalid token kinds are ignored
	}, {
		err:  &SemanticError{action: "marshal", JSONKind: '"'},
		want: `json: cannot marshal JSON string`,
	}, {
		err:  &SemanticError{GoType: T[bool]()},
		want: `json: cannot handle Go bool`,
	}, {
		err:  &SemanticError{action: "marshal", GoType: T[int]()},
		want: `json: cannot marshal from Go int`,
	}, {
		err:  &SemanticError{action: "unmarshal", GoType: T[uint]()},
		want: `json: cannot unmarshal into Go uint`,
	}, {
		err:  &SemanticError{GoType: T[struct{ Alpha, Bravo, Charlie, Delta, Echo, Foxtrot, Golf, Hotel string }]()},
		want: `json: cannot handle Go struct`,
	}, {
		err:  &SemanticError{GoType: T[struct{ Alpha, Bravo, Charlie, Delta, Echo, Foxtrot, Golf, Hotel, x string }]()},
		want: `json: cannot handle Go json.struct`,
	}, {
		err:  &SemanticError{JSONKind: '0', GoType: T[tar.Header]()},
		want: `json: cannot handle JSON number with Go tar.Header`,
	}, {
		err:  &SemanticError{action: "unmarshal", JSONKind: '0', JSONValue: jsontext.Value(`1e1000`), GoType: T[int]()},
		want: `json: cannot unmarshal JSON number 1e1000 into Go int`,
	}, {
		err:  &SemanticError{action: "marshal", JSONKind: '{', GoType: T[bytes.Buffer]()},
		want: `json: cannot marshal JSON object from Go bytes.Buffer`,
	}, {
		err:  &SemanticError{action: "unmarshal", JSONKind: ']', GoType: T[strings.Reader]()},
		want: `json: cannot unmarshal JSON array into Go strings.Reader`,
	}, {
		err:  &SemanticError{action: "unmarshal", JSONKind: '{', GoType: T[float64](), ByteOffset: 123},
		want: `json: cannot unmarshal JSON object into Go float64 after offset 123`,
	}, {
		err:  &SemanticError{action: "marshal", JSONKind: 'f', GoType: T[complex128](), ByteOffset: 123, JSONPointer: "/foo/2/bar/3"},
		want: `json: cannot marshal JSON boolean from Go complex128 within "/foo/2/bar/3"`,
	}, {
		err:  &SemanticError{action: "unmarshal", JSONKind: '}', GoType: T[io.Reader](), ByteOffset: 123, JSONPointer: "/foo/2/bar/3", Err: errors.New("some underlying error")},
		want: `json: cannot unmarshal JSON object into Go io.Reader within "/foo/2/bar/3": some underlying error`,
	}, {
		err:  &SemanticError{Err: errors.New("some underlying error")},
		want: `json: cannot handle: some underlying error`,
	}, {
		err:  &SemanticError{ByteOffset: 123},
		want: `json: cannot handle after offset 123`,
	}, {
		err:  &SemanticError{JSONPointer: "/foo/2/bar/3"},
		want: `json: cannot handle within "/foo/2/bar/3"`,
	}, {
		err:  &SemanticError{action: "unmarshal", JSONPointer: "/3", GoType: T[struct{ Fizz, Buzz string }](), Err: ErrUnknownName},
		want: `json: cannot unmarshal into Go struct { Fizz string; Buzz string }: unknown object member name "3"`,
	}, {
		err:  &SemanticError{action: "unmarshal", JSONPointer: "/foo/2/bar/3", GoType: T[struct{ Foo string }](), Err: ErrUnknownName},
		want: `json: cannot unmarshal into Go struct { Foo string }: unknown object member name "3" within "/foo/2/bar"`,
	}, {
		err:  &SemanticError{JSONPointer: "/foo/bar", ByteOffset: 16, GoType: T[string](), Err: &jsontext.SyntacticError{JSONPointer: "/foo/bar/baz", ByteOffset: 53, Err: jsonwire.ErrInvalidUTF8}},
		want: `json: cannot handle Go string: invalid UTF-8 within "/foo/bar/baz" after offset 53`,
	}, {
		err:  &SemanticError{JSONPointer: "/fizz/bar", ByteOffset: 16, GoType: T[string](), Err: &jsontext.SyntacticError{JSONPointer: "/foo/bar/baz", ByteOffset: 53, Err: jsonwire.ErrInvalidUTF8}},
		want: `json: cannot handle Go string within "/fizz/bar": invalid UTF-8 within "/foo/bar/baz" after offset 53`,
	}, {
		err:  &SemanticError{ByteOffset: 16, GoType: T[string](), Err: &jsontext.SyntacticError{JSONPointer: "/foo/bar/baz", ByteOffset: 53, Err: jsonwire.ErrInvalidUTF8}},
		want: `json: cannot handle Go string: invalid UTF-8 within "/foo/bar/baz" after offset 53`,
	}, {
		err:  &SemanticError{ByteOffset: 85, GoType: T[string](), Err: &jsontext.SyntacticError{JSONPointer: "/foo/bar/baz", ByteOffset: 53, Err: jsonwire.ErrInvalidUTF8}},
		want: `json: cannot handle Go string after offset 85: invalid UTF-8 within "/foo/bar/baz" after offset 53`,
	}}

	for _, tt := range tests {
		got := tt.err.Error()
		// Cleanup the error of non-deterministic rendering effects.
		if strings.HasPrefix(got, errorPrefix+"unable to ") {
			got = errorPrefix + "cannot " + strings.TrimPrefix(got, errorPrefix+"unable to ")
		}
		if got != tt.want {
			t.Errorf("%#v.Error mismatch:\ngot  %v\nwant %v", tt.err, got, tt.want)
		}
	}
}
