package script

import (
	"log"

	tengo "github.com/d5/tengo/v2"
	"github.com/d5/tengo/v2/token"

	"git.autistici.org/ai3/tools/iprep/ext"
)

// Tengo object type that wraps a read-only map[string]int64, without
// requiring us to go through a map[string]interface{}.
type intMap map[string]int64

func (m intMap) String() string {
	return "<intMap>"
}

func (m intMap) TypeName() string {
	return "int-map"
}

func (m intMap) Copy() tengo.Object {
	return m
}

func (m intMap) IsFalsy() bool {
	return len(m) == 0
}

func (m intMap) Equals(o tengo.Object) bool {
	return false
}

func (m intMap) BinaryOp(op token.Token, rhs tengo.Object) (tengo.Object, error) {
	return nil, tengo.ErrInvalidOperator
}

func (m intMap) IndexGet(index tengo.Object) (tengo.Object, error) {
	indexStr, ok := index.(*tengo.String)
	if !ok {
		return nil, tengo.ErrInvalidIndexType
	}

	// value, ok := m[indexStr.Value]
	// if !ok {
	// 	return tengo.UndefinedValue, nil
	// }

	value := m[indexStr.Value]
	return &tengo.Int{Value: value}, nil
}

func (m intMap) IndexSet(index, value tengo.Object) error {
	return tengo.ErrNotIndexAssignable
}

func (m intMap) Iterate() tengo.Iterator {
	return newIntMapIterator(m)
}

func (m intMap) Call(args ...tengo.Object) (ret tengo.Object, err error) { return }

func (m intMap) CanCall() bool { return false }

func (m intMap) CanIterate() bool { return false }

// We need to make a copy of the map to iterate on it with this API.
// Note that in the iterator, 'idx' maps to the *next* item.
type intMapIterator struct {
	arr []keyIntPair
	idx int
}

type keyIntPair struct {
	key   string
	value int64
}

func newIntMapIterator(m intMap) *intMapIterator {
	arr := make([]keyIntPair, 0, len(m))
	for k, v := range m {
		arr = append(arr, keyIntPair{key: k, value: v})
	}
	return &intMapIterator{arr: arr}
}

func (i *intMapIterator) String() string {
	return "<intMapIterator>"
}

func (i *intMapIterator) TypeName() string {
	return "int-map-iterator"
}

func (i *intMapIterator) Copy() tengo.Object {
	return i
}

func (i *intMapIterator) IsFalsy() bool {
	return len(i.arr) == 0
}

func (i *intMapIterator) Equals(o tengo.Object) bool {
	return false
}

func (i *intMapIterator) BinaryOp(op token.Token, rhs tengo.Object) (tengo.Object, error) {
	return nil, tengo.ErrInvalidOperator
}

func (i *intMapIterator) Next() bool {
	i.idx++
	return i.idx <= len(i.arr)
}

func (i *intMapIterator) Key() tengo.Object {
	return &tengo.String{Value: i.arr[i.idx-1].key}
}

func (i *intMapIterator) Value() tengo.Object {
	return &tengo.Int{Value: i.arr[i.idx-1].value}
}

func (*intMapIterator) IndexGet(index tengo.Object) (tengo.Object, error) {
	return nil, tengo.ErrNotIndexable
}

func (*intMapIterator) IndexSet(index, value tengo.Object) error {
	return tengo.ErrNotIndexAssignable
}

func (*intMapIterator) Iterate() tengo.Iterator                                 { return nil }
func (*intMapIterator) Call(args ...tengo.Object) (ret tengo.Object, err error) { return }
func (*intMapIterator) CanCall() bool                                           { return false }
func (*intMapIterator) CanIterate() bool                                        { return false }

// A Tengo callable to look up data in external sources.
type extLookupFunc map[string]ext.ExternalSource

func (f extLookupFunc) String() string {
	return "<extLookupFunc>"
}

func (f extLookupFunc) TypeName() string {
	return "ext-lookup-func"
}

func (f extLookupFunc) Copy() tengo.Object {
	return f
}

func (f extLookupFunc) IsFalsy() bool {
	return len(f) == 0
}

func (f extLookupFunc) Equals(o tengo.Object) bool {
	return false
}

func (f extLookupFunc) BinaryOp(op token.Token, rhs tengo.Object) (tengo.Object, error) {
	return nil, tengo.ErrInvalidOperator
}

func (f extLookupFunc) Call(args ...tengo.Object) (ret tengo.Object, err error) {
	// Expected arguments: source name, IP.
	if len(args) != 2 {
		return nil, tengo.ErrWrongNumArguments
	}

	name, ok := tengo.ToString(args[0])
	if !ok {
		return nil, tengo.ErrInvalidArgumentType{
			Name:     "source_name",
			Expected: "string",
			Found:    args[0].TypeName(),
		}
	}

	ip, ok := tengo.ToString(args[1])
	if !ok {
		return nil, tengo.ErrInvalidArgumentType{
			Name:     "ip",
			Expected: "string",
			Found:    args[1].TypeName(),
		}
	}

	// Invoke the external source lookup method.
	if f == nil {
		return nil, tengo.ErrInvalidIndexValueType
	}
	src, ok := f[name]
	if !ok {
		return nil, tengo.ErrInvalidIndexValueType
	}
	result, err := src.LookupIP(ip)
	if err != nil {
		log.Printf("external source lookup error: ip=%s: %v", ip, err)
		return tengo.UndefinedValue, nil
	}
	return result, nil
}

func (extLookupFunc) IndexGet(index tengo.Object) (tengo.Object, error) {
	return nil, tengo.ErrNotIndexable
}

func (extLookupFunc) IndexSet(index, value tengo.Object) error {
	return tengo.ErrNotIndexAssignable
}

func (extLookupFunc) Iterate() tengo.Iterator { return nil }
func (extLookupFunc) CanCall() bool           { return true }
func (extLookupFunc) CanIterate() bool        { return false }