package settings

import (
	"encoding/json"
	"fmt"
	"os"
	"path"
	"reflect"
	"strconv"
	"strings"
	"time"
)

// DiffSettings returns only the fields that differs between the two objects.
func DiffSettings(old, new *Settings) (ret map[string]interface{}) {
	ret = map[string]interface{}{}
	for _, field := range reflect.VisibleFields(reflect.TypeOf(*old)) {
		if !reflect.DeepEqual(reflect.ValueOf(*old).FieldByName(field.Name).Interface(), reflect.ValueOf(*new).FieldByName(field.Name).Interface()) {
			name := field.Name

			if tag, ok := field.Tag.Lookup("json"); ok {
				name = strings.Split(tag, ",")[0]
			}

			ret[name] = reflect.ValueOf(*new).FieldByName(field.Name).Interface()
		}
	}

	return
}

type NextSettingsFile struct {
	Id     int64                  `json:"id"`
	Date   time.Time              `json:"date"`
	Values map[string]interface{} `json:"values"`
}

func ReadNextSettingsFile(filename string, ts int64) (*NextSettingsFile, error) {
	fd, err := os.Open(filename)
	if err != nil {
		return nil, fmt.Errorf("unable to open(%s): %w", filename, err)
	} else {
		defer fd.Close()

		var s map[string]interface{}

		jdec := json.NewDecoder(fd)

		if err := jdec.Decode(&s); err != nil {
			return nil, fmt.Errorf("an error occurs during JSON decoding of %s: %w", filename, err)
		}

		return &NextSettingsFile{
			Id:     ts,
			Date:   time.Unix(ts, 0),
			Values: s,
		}, nil
	}
}

func ListNextSettingsFiles() ([]*NextSettingsFile, error) {
	files, err := os.ReadDir(SettingsDir)
	if err != nil {
		return nil, err
	}

	var ret []*NextSettingsFile
	for _, file := range files {
		if len(file.Name()) < 10 {
			continue
		}
		ts, err := strconv.ParseInt(file.Name()[:10], 10, 64)
		if err == nil {
			nsf, err := ReadNextSettingsFile(path.Join(SettingsDir, file.Name()), ts)
			if err != nil {
				return nil, err
			}

			ret = append(ret, nsf)
		}
	}

	return ret, nil
}

func MergeNextSettingsUntil(until *time.Time) (map[string]interface{}, error) {
	nsfs, err := ListNextSettingsFiles()
	if err != nil {
		return nil, err
	}

	ret := map[string]interface{}{}
	for _, nsf := range nsfs {
		if until == nil || time.Unix(nsf.Id, 0).Before(*until) {
			for k, v := range nsf.Values {
				ret[k] = v
			}
		}
	}

	return ret, nil
}

func MergeSettings(current Settings, new map[string]interface{}) *Settings {
	for _, field := range reflect.VisibleFields(reflect.TypeOf(current)) {
		name := field.Name

		if tag, ok := field.Tag.Lookup("json"); ok {
			name = strings.Split(tag, ",")[0]
		}

		if v, ok := new[name]; ok {
			if reflect.TypeOf(v) != reflect.ValueOf(&current).Elem().FieldByName(field.Name).Type() {
				nv := reflect.New(reflect.ValueOf(&current).Elem().FieldByName(field.Name).Type())
				mv, _ := json.Marshal(v)
				json.Unmarshal(mv, nv.Interface())
				v = nv.Elem().Interface()
			}

			reflect.ValueOf(&current).Elem().FieldByName(field.Name).Set(reflect.ValueOf(v))
		}
	}

	return &current
}
