// SPDX-FileCopyrightText: Copyright 2015-2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0

package diff

import (
	"iter"
	"reflect"
	"slices"
	"testing"
	"time"

	"github.com/go-openapi/testify/v2/assert"

	"github.com/go-openapi/spec"
)

func Test_getRef(t *testing.T) {
	type args struct {
		item any
	}
	aRef, _ := spec.NewRef("hello")
	tests := []struct {
		name string
		args args
		want spec.Ref
	}{
		{
			name: "rando object",
			args: args{item: "bob"},
			want: spec.Ref{},
		},
		{
			name: "refable",
			args: args{&spec.Refable{Ref: aRef}},
			want: aRef,
		},
		{
			name: "schema",
			args: args{&spec.Schema{SchemaProps: spec.SchemaProps{Ref: aRef}}},
			want: aRef,
		},
		{
			name: "schemaProps",
			args: args{&spec.SchemaProps{Ref: aRef}},
			want: aRef,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := getRef(tt.args.item); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("getRef() = %v, want %v", got, tt.want)
			}
		})
	}
}

// func TestCheckToFromArrayType(t *testing.T) {
// 	type args struct {
// 		diffs []TypeDiff
// 		type1 interface{}
// 		type2 interface{}
// 	}
// 	tests := []struct {
// 		name string
// 		args
// 		want []TypeDiff
// 	}{
// 		{
// 			name: "to",
// 			args: args{
// 				type1: spec.Int32Property(),
// 				type2: arraySchemaOf("string"),
// 			},
// 			want: []TypeDiff{{Change: ChangedType, FromType: "<integer>", ToType: "<array[string]>"}},
// 		},
// 		{
// 			name: "from",
// 			args: args{
// 				type1: arraySchemaOf("string"),
// 				type2: spec.Int32Property(),
// 			},
// 			want: []TypeDiff{{Change: ChangedType, ToType: "<integer>", FromType: "<array[string]>"}},
// 		},
// 	}
// 	for _, tt := range tests {
// 		t.Run(tt.name, func(t *testing.T) {
// 			if got := CheckToFromArrayType(tt.args.diffs, tt.args.type1, tt.args.type2); !reflect.DeepEqual(got, tt.want) {
// 				t.Errorf("CheckToFromArrayType() = %s, want %s", jsonStr(got), jsonStr(tt.want))
// 			}
// 		})
// 	}
// }

/*
func arraySchemaOf(typename string) *spec.Schema {
	return &spec.Schema{SchemaProps: spec.SchemaProps{
		Type: spec.StringOrArray{"array"},
		Items: &spec.SchemaOrArray{
			Schema: &spec.Schema{
				SchemaProps: spec.SchemaProps{
					Type: spec.StringOrArray{typename}}}},
	},
	}
}
*/

func TestCheckToFromPrimitiveType(t *testing.T) {
	type args struct {
		diffs []TypeDiff
		type1 any
		type2 any
	}
	tests := []struct {
		name string
		args args
		want []TypeDiff
	}{
		// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := CheckToFromPrimitiveType(tt.args.diffs, tt.args.type1, tt.args.type2); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("CheckToFromPrimitiveType() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestCheckRefChange(t *testing.T) {
	type args struct {
		diffs []TypeDiff
		type1 any
		type2 any
	}
	tests := []struct {
		name           string
		args           args
		wantDiffReturn []TypeDiff
	}{
		{
			name: "reftarget",
			args: args{
				type1: spec.RefProperty("#/definitions/FirstObject"),
				type2: spec.RefProperty("#/definitions/SecondObject"),
			},
			wantDiffReturn: []TypeDiff{{Change: RefTargetChanged, FromType: "<FirstObject>", ToType: "<SecondObject>"}},
		},
		{
			name: "toref",
			args: args{
				type1: spec.Int32Property(),
				type2: spec.RefProperty("#/definitions/SecondObject"),
			},
			wantDiffReturn: []TypeDiff{{Change: ChangedType, FromType: "<integer>", ToType: "<SecondObject>"}},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if gotDiffReturn := CheckRefChange(tt.args.diffs, tt.args.type1, tt.args.type2); !reflect.DeepEqual(gotDiffReturn, tt.wantDiffReturn) {
				t.Errorf("CheckRefChange() = %s, want %s", jsonStr(gotDiffReturn), jsonStr(tt.wantDiffReturn))
			}
		})
	}
}

func Test_isRef(t *testing.T) {
	r := spec.RefSchema("#/definitions/Bob")
	p := spec.Int16Property()

	assert.TrueT(t, isRefType(r))
	assert.FalseT(t, isRefType(p))

	refb := spec.Refable{Ref: r.Ref}
	assert.TrueT(t, isRefType(refb))

	ss := spec.SimpleSchema{}
	assert.FalseT(t, isRefType(&ss))

	ro := time.Timer{}
	assert.FalseT(t, isRefType(ro))
}

func Test_compareEnums(t *testing.T) {
	type args struct {
		left  []any
		right []any
	}
	tests := []struct {
		name string
		args args
		want []TypeDiff
	}{
		// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := CompareEnums(tt.args.left, tt.args.right); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("compareEnums() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_checkNumericTypeChanges(t *testing.T) {
	type args struct {
		diffs []TypeDiff
		type1 *spec.SchemaProps
		type2 *spec.SchemaProps
	}
	tests := []struct {
		name string
		args args
		want []TypeDiff
	}{
		{
			name: "ExclusiveMin",
			args: args{
				type1: &spec.Int32Property().WithMinimum(100, true).SchemaProps,
				type2: &spec.Int32Property().SchemaProps,
			},
			want: []TypeDiff{{Change: WidenedType, Description: "Exclusive Minimum Removed:false->false"}},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := checkNumericTypeChanges(tt.args.diffs, tt.args.type1, tt.args.type2); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("checkNumericTypeChanges() = %s, want %s", jsonStr(got), jsonStr(tt.want))
			}
		})
	}
}

type compareValueCase struct {
	name       string
	fieldName  string
	wantChange SpecChangeCode
}

func compareValueCases() iter.Seq[compareValueCase] {
	return slices.Values([]compareValueCase{
		{name: "both null", fieldName: "bob", wantChange: NoChangeDetected},
		{name: "greater", fieldName: "bob", wantChange: WidenedType},
		{name: "less", fieldName: "bob", wantChange: NarrowedType},
		{name: "firstNil", fieldName: "bob", wantChange: AddedConstraint},
		{name: "secondNil", fieldName: "bob", wantChange: DeletedConstraint},
	})
}

func TestCompareFloatValues(t *testing.T) {
	floatInputs := map[string]struct{ field1, field2 *float64 }{
		"both null": {nil, nil},
		"greater":   {floatPointerOf(1.0), floatPointerOf(2.0)},
		"less":      {floatPointerOf(2.0), floatPointerOf(1.0)},
		"firstNil":  {nil, floatPointerOf(1.0)},
		"secondNil": {floatPointerOf(2.0), nil},
	}

	for tc := range compareValueCases() {
		in := floatInputs[tc.name]
		t.Run(tc.name, func(t *testing.T) {
			got := CompareFloatValues(tc.fieldName, in.field1, in.field2, WidenedType, NarrowedType)
			if tc.wantChange == NoChangeDetected {
				assert.Empty(t, got)
			} else {
				assert.Len(t, got, 1)
				assert.EqualT(t, tc.wantChange, got[0].Change)
			}
		})
	}
}

func TestCompareIntValues(t *testing.T) {
	intInputs := map[string]struct{ field1, field2 *int64 }{
		"both null": {nil, nil},
		"greater":   {intPointerOf(1), intPointerOf(2)},
		"less":      {intPointerOf(2), intPointerOf(1)},
		"firstNil":  {nil, intPointerOf(1)},
		"secondNil": {intPointerOf(2), nil},
	}

	for tc := range compareValueCases() {
		in := intInputs[tc.name]
		t.Run(tc.name, func(t *testing.T) {
			got := CompareIntValues(tc.fieldName, in.field1, in.field2, WidenedType, NarrowedType)
			if tc.wantChange == NoChangeDetected {
				assert.Empty(t, got)
			} else {
				assert.Len(t, got, 1)
				assert.EqualT(t, tc.wantChange, got[0].Change)
			}
		})
	}
}

func floatPointerOf(f float64) *float64 {
	return &f
}

func intPointerOf(f int64) *int64 {
	return &f
}

func TestCheckToFromRequired(t *testing.T) {
	type args struct {
		required1 bool
		required2 bool
	}
	tests := []struct {
		name      string
		args      args
		wantDiffs []TypeDiff
	}{
		// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if gotDiffs := CheckToFromRequired(tt.args.required1, tt.args.required2); !reflect.DeepEqual(gotDiffs, tt.wantDiffs) {
				t.Errorf("CheckToFromRequired() = %v, want %v", gotDiffs, tt.wantDiffs)
			}
		})
	}
}

func Test_compareProperties(t *testing.T) {
	type args struct {
		location  DifferenceLocation
		schema1   *spec.Schema
		schema2   *spec.Schema
		getRefFn1 SchemaFromRefFn
		getRefFn2 SchemaFromRefFn
		cmp       CompareSchemaFn
	}
	tests := []struct {
		name string
		args args
		want []SpecDifference
	}{
		// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := CompareProperties(tt.args.location, tt.args.schema1, tt.args.schema2, tt.args.getRefFn1, tt.args.getRefFn2, tt.args.cmp); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("compareProperties() = %v, want %v", got, tt.want)
			}
		})
	}
}

func Test_propertiesFor(t *testing.T) {
	type args struct {
		schema   *spec.Schema
		getRefFn SchemaFromRefFn
	}
	tests := []struct {
		name string
		args args
		want PropertyMap
	}{
		// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := propertiesFor(tt.args.schema, tt.args.getRefFn); !reflect.DeepEqual(got, tt.want) {
				t.Errorf("propertiesFor() = %v, want %v", got, tt.want)
			}
		})
	}
}

func jsonStr(thing any) string {
	bstr, _ := JSONMarshal(thing)
	return string(bstr)
}
