package copier_test

import (
	"errors"
	"strconv"
	"testing"
	"time"

	"github.com/jinzhu/copier"
)

func TestCopyWithTypeConverters(t *testing.T) {
	type SrcStruct struct {
		Field1 time.Time
		Field2 *time.Time
		Field3 *time.Time
		Field4 string
	}

	type DestStruct struct {
		Field1 string
		Field2 string
		Field3 string
		Field4 int
	}

	testTime := time.Date(2021, 3, 5, 1, 30, 0, 123000000, time.UTC)

	src := SrcStruct{
		Field1: testTime,
		Field2: &testTime,
		Field3: nil,
		Field4: "9000",
	}

	var dst DestStruct

	err := copier.CopyWithOption(&dst, &src, copier.Option{
		IgnoreEmpty: true,
		DeepCopy:    true,
		Converters: []copier.TypeConverter{
			{
				SrcType: time.Time{},
				DstType: copier.String,
				Fn: func(src interface{}) (interface{}, error) {
					s, ok := src.(time.Time)

					if !ok {
						return nil, errors.New("src type not matching")
					}

					return s.Format(time.RFC3339), nil
				},
			},
			{
				SrcType: copier.String,
				DstType: copier.Int,
				Fn: func(src interface{}) (interface{}, error) {
					s, ok := src.(string)

					if !ok {
						return nil, errors.New("src type not matching")
					}

					return strconv.Atoi(s)
				},
			},
		},
	})
	if err != nil {
		t.Fatalf(`Should be able to copy from src to dst object. %v`, err)
		return
	}

	dateStr := "2021-03-05T01:30:00Z"

	if dst.Field1 != dateStr {
		t.Fatalf("got %q, wanted %q", dst.Field1, dateStr)
	}

	if dst.Field2 != dateStr {
		t.Fatalf("got %q, wanted %q", dst.Field2, dateStr)
	}

	if dst.Field3 != "" {
		t.Fatalf("got %q, wanted %q", dst.Field3, "")
	}

	if dst.Field4 != 9000 {
		t.Fatalf("got %q, wanted %q", dst.Field4, 9000)
	}
}

func TestCopyWithConverterAndAnnotation(t *testing.T) {
	type SrcStruct struct {
		Field1 string
	}

	type DestStruct struct {
		Field1 string
		Field2 string `copier:"Field1"`
	}

	src := SrcStruct{
		Field1: "test",
	}

	var dst DestStruct

	err := copier.CopyWithOption(&dst, &src, copier.Option{
		IgnoreEmpty: true,
		DeepCopy:    true,
		Converters: []copier.TypeConverter{
			{
				SrcType: copier.String,
				DstType: copier.String,
				Fn: func(src interface{}) (interface{}, error) {
					s, ok := src.(string)

					if !ok {
						return nil, errors.New("src type not matching")
					}

					return s + "2", nil
				},
			},
		},
	})
	if err != nil {
		t.Fatalf(`Should be able to copy from src to dst object. %v`, err)
		return
	}

	if dst.Field2 != "test2" {
		t.Fatalf("got %q, wanted %q", dst.Field2, "test2")
	}
}

func TestCopyWithConverterStrToStrPointer(t *testing.T) {
	type SrcStruct struct {
		Field1 string
	}

	type DestStruct struct {
		Field1 *string
	}

	src := SrcStruct{
		Field1: "",
	}

	var dst DestStruct

	ptrStrType := ""

	err := copier.CopyWithOption(&dst, &src, copier.Option{
		IgnoreEmpty: true,
		DeepCopy:    true,
		Converters: []copier.TypeConverter{
			{
				SrcType: copier.String,
				DstType: &ptrStrType,
				Fn: func(src interface{}) (interface{}, error) {
					s, _ := src.(string)

					// return nil on empty string
					if s == "" {
						return nil, nil
					}

					return &s, nil
				},
			},
		},
	})
	if err != nil {
		t.Fatalf(`Should be able to copy from src to dst object. %v`, err)
		return
	}

	if dst.Field1 != nil {
		t.Fatalf("got %q, wanted nil", *dst.Field1)
	}
}

func TestCopyWithConverterRaisingError(t *testing.T) {
	type SrcStruct struct {
		Field1 string
	}

	type DestStruct struct {
		Field1 *string
	}

	src := SrcStruct{
		Field1: "",
	}

	var dst DestStruct

	ptrStrType := ""

	err := copier.CopyWithOption(&dst, &src, copier.Option{
		IgnoreEmpty: false,
		DeepCopy:    true,
		Converters: []copier.TypeConverter{
			{
				SrcType: copier.String,
				DstType: &ptrStrType,
				Fn: func(src interface{}) (interface{}, error) {
					return nil, errors.New("src type not matching")
				},
			},
		},
	})
	if err == nil {
		t.Fatalf(`Should be raising an error.`)
		return
	}
}
