package abi

import (
	"fmt"
	"testing"

	"go/types"
)

const (
	MAXKEYSIZE  = 128
	MAXELEMSIZE = 128
	BUCKETSIZE  = 8
)

type mockSizes struct {
	alignof func(types.Type) int64
	sizeof  func(types.Type) int64
}

func (m mockSizes) Alignof(t types.Type) int64 { return m.alignof(t) }
func (m mockSizes) Sizeof(t types.Type) int64  { return m.sizeof(t) }
func (m mockSizes) Offsetsof(f []*types.Var) []int64 {
	// Mock implementation for testing alignment checks
	return []int64{0, 8, 16} // Typical offsets for bucket fields
}

// isPointer reports whether t is a pointer type.
func isPointer(t types.Type) (ok bool) {
	_, ok = t.Underlying().(*types.Pointer)
	return
}

// MapTypeFlags computes the flags for the given map type.
func MapTypeFlags(t *types.Map, sizes types.Sizes) (flags int) {
	if sizes.Sizeof(t.Key()) > MAXKEYSIZE {
		flags |= 1 // indirect key
	}
	if sizes.Sizeof(t.Elem()) > MAXELEMSIZE {
		flags |= 2 // indirect value
	}
	return
}

// MapBucketType returns the bucket type for a map.
func MapBucketType(t *types.Map, sizes types.Sizes) *types.Struct {
	keytype := t.Key()
	elemtype := t.Elem()
	bucket := types.NewStruct([]*types.Var{}, nil)

	if !types.Comparable(keytype) {
		log.Fatalf("unsupported map key type for %v", t)
	}
	if BUCKETSIZE < 8 {
		log.Fatalf("bucket size %d too small for proper alignment %d", BUCKETSIZE, 8)
	}
	if uint8(sizes.Alignof(keytype)) > BUCKETSIZE {
		log.Fatalf("key align too big for %v", t)
	}
	if uint8(sizes.Alignof(elemtype)) > BUCKETSIZE {
		log.Fatalf("elem align %d too big for %v, BUCKETSIZE=%d", sizes.Alignof(elemtype), t, BUCKETSIZE)
	}
	if sizes.Alignof(keytype) > MAXKEYSIZE {
		log.Fatalf("key align too big for %v", t)
	}
	if sizes.Alignof(elemtype) > MAXELEMSIZE {
		log.Fatalf("elem align too big for %v", t)
	}
	if sizes.Alignof(keytype) > MAXKEYSIZE && !isPointer(keytype) {
		log.Fatalf("key indirect incorrect for %v", t)
	}
	if sizes.Alignof(elemtype) > MAXELEMSIZE && !isPointer(elemtype) {
		log.Fatalf("elem indirect incorrect for %v", t)
	}
	if sizes.Sizeof(keytype)%sizes.Alignof(keytype) != 0 {
		log.Fatalf("key size not a multiple of key align for %v", t)
	}
	if sizes.Sizeof(elemtype)%sizes.Alignof(elemtype) != 0 {
		log.Fatalf("elem size not a multiple of elem align for %v", t)
	}
	if uint8(sizes.Alignof(bucket))%uint8(sizes.Alignof(keytype)) != 0 {
		log.Fatalf("bucket align not multiple of key align %v", t)
	}
	if uint8(sizes.Alignof(bucket))%uint8(sizes.Alignof(elemtype)) != 0 {
		log.Fatalf("bucket align not multiple of elem align %v", t)
	}
	offs := sizes.Offsetsof(nil)
	if len(offs) >= 3 && offs[1]%sizes.Alignof(keytype) != 0 {
		log.Fatalf("bad alignment of keys in bmap for %v", t)
	}
	if len(offs) >= 3 && offs[2]%sizes.Alignof(elemtype) != 0 {
		log.Fatalf("bad alignment of elems in bmap for %v", t)
	}

	return bucket
}

// logger interface for testing
type logger interface {
	Fatalf(format string, args ...interface{})
}

var log logger = &defaultLogger{}

type defaultLogger struct{}

func (l *defaultLogger) Fatalf(format string, args ...interface{}) {
	panic(fmt.Sprintf(format, args...))
}

type testLogger struct {
	t *testing.T
}

func (l *testLogger) Fatalf(format string, args ...interface{}) {
	msg := fmt.Sprintf(format, args...)
	panic(msg)
}

func TestIsPointer(t *testing.T) {
	tests := []struct {
		name string
		typ  types.Type
		want bool
	}{
		{
			name: "pointer type",
			typ:  types.NewPointer(types.Typ[types.Int]),
			want: true,
		},
		{
			name: "non-pointer type",
			typ:  types.Typ[types.Int],
			want: false,
		},
		{
			name: "pointer to struct",
			typ:  types.NewPointer(types.NewStruct([]*types.Var{}, nil)),
			want: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := isPointer(tt.typ); got != tt.want {
				t.Errorf("isPointer() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestMapTypeFlags(t *testing.T) {
	tests := []struct {
		name     string
		key      types.Type
		elem     types.Type
		sizeFn   func(types.Type) int64
		alignFn  func(types.Type) int64
		wantFlag int
	}{
		{
			name: "small key and elem",
			key:  types.Typ[types.Int],
			elem: types.Typ[types.Int],
			sizeFn: func(t types.Type) int64 {
				return 8 // 64-bit integers
			},
			alignFn: func(t types.Type) int64 {
				return 8
			},
			wantFlag: 0,
		},
		{
			name: "large key",
			key:  types.NewArray(types.Typ[types.Int64], 20), // 160 bytes
			elem: types.Typ[types.Int],
			sizeFn: func(t types.Type) int64 {
				if _, ok := t.(*types.Array); ok {
					return 160
				}
				return 8
			},
			alignFn: func(t types.Type) int64 {
				return 8
			},
			wantFlag: 1, // indirect key
		},
		{
			name: "large elem",
			key:  types.Typ[types.Int],
			elem: types.NewArray(types.Typ[types.Int64], 20), // 160 bytes
			sizeFn: func(t types.Type) int64 {
				if _, ok := t.(*types.Array); ok {
					return 160
				}
				return 8
			},
			alignFn: func(t types.Type) int64 {
				return 8
			},
			wantFlag: 2, // indirect elem
		},
		{
			name: "large key and elem",
			key:  types.NewArray(types.Typ[types.Int64], 20), // 160 bytes
			elem: types.NewArray(types.Typ[types.Int64], 20), // 160 bytes
			sizeFn: func(t types.Type) int64 {
				if _, ok := t.(*types.Array); ok {
					return 160
				}
				return 8
			},
			alignFn: func(t types.Type) int64 {
				return 8
			},
			wantFlag: 3, // both indirect
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sizes := &mockSizes{
				sizeof:  tt.sizeFn,
				alignof: tt.alignFn,
			}
			mapType := types.NewMap(tt.key, tt.elem)
			if got := MapTypeFlags(mapType, sizes); got != tt.wantFlag {
				t.Errorf("MapTypeFlags() = %v, want %v", got, tt.wantFlag)
			}
		})
	}
}

func TestMapTypeChecks(t *testing.T) {
	origLog := log
	defer func() { log = origLog }()

	tests := []struct {
		name    string
		setup   func() (*types.Map, types.Sizes)
		wantErr bool
	}{
		{
			name: "non-comparable key type",
			setup: func() (*types.Map, types.Sizes) {
				key := types.NewSlice(types.Typ[types.Int])
				elem := types.Typ[types.Int]
				return types.NewMap(key, elem), &mockSizes{
					sizeof:  func(t types.Type) int64 { return 8 },
					alignof: func(t types.Type) int64 { return 8 },
				}
			},
			wantErr: true,
		},
		{
			name: "key align too big",
			setup: func() (*types.Map, types.Sizes) {
				return types.NewMap(types.Typ[types.Int], types.Typ[types.Int]), &mockSizes{
					sizeof: func(t types.Type) int64 { return 8 },
					alignof: func(t types.Type) int64 {
						if _, ok := t.(*types.Basic); ok && t.String() == "int" {
							return BUCKETSIZE + 1
						}
						return 8
					},
				}
			},
			wantErr: true,
		},
		{
			name: "key size not multiple of align",
			setup: func() (*types.Map, types.Sizes) {
				return types.NewMap(types.Typ[types.Int], types.Typ[types.Int]), &mockSizes{
					sizeof: func(t types.Type) int64 {
						if _, ok := t.(*types.Basic); ok && t.String() == "int" {
							return 7
						}
						return 8
					},
					alignof: func(t types.Type) int64 {
						if _, ok := t.(*types.Basic); ok && t.String() == "int" {
							return 4
						}
						return 8
					},
				}
			},
			wantErr: true,
		},
		{
			name: "elem align too big",
			setup: func() (*types.Map, types.Sizes) {
				return types.NewMap(types.Typ[types.Int], types.Typ[types.Int]), &mockSizes{
					sizeof: func(t types.Type) int64 { return 8 },
					alignof: func(t types.Type) int64 {
						if _, ok := t.(*types.Basic); ok && t.String() == "int" {
							return 4
						}
						return BUCKETSIZE + 1
					},
				}
			},
			wantErr: true,
		},
		{
			name: "valid map type",
			setup: func() (*types.Map, types.Sizes) {
				return types.NewMap(types.Typ[types.Int], types.Typ[types.Int]), &mockSizes{
					sizeof:  func(t types.Type) int64 { return 8 },
					alignof: func(t types.Type) int64 { return 8 },
				}
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			log = &testLogger{t: t}
			defer func() {
				if r := recover(); r != nil {
					if !tt.wantErr {
						t.Errorf("MapBucketType() panicked unexpectedly: %v", r)
					}
				} else if tt.wantErr {
					t.Error("expected panic but got none")
				}
			}()

			mapType, sizes := tt.setup()
			MapBucketType(mapType, sizes)
		})
	}
}
