package virtbuf

import (
	// "reflect"

	"errors"
	"fmt"
	"time"

	"google.golang.org/protobuf/types/known/anypb"
	"google.golang.org/protobuf/types/known/timestamppb"
	"google.golang.org/protobuf/types/known/wrapperspb"

	"go.wit.com/log"
)

func convertToAnypb(x any) *anypb.Any {
	switch v := x.(type) {
	case int64:
		var a *anypb.Any
		a, _ = anypb.New(wrapperspb.Int64(x.(int64)))
		return a
	case string:
		var a *anypb.Any
		a, _ = anypb.New(wrapperspb.String(x.(string)))
		return a
	case int:
		var a *anypb.Any
		a, _ = anypb.New(wrapperspb.Int64(x.(int64)))
		return a
	case bool:
		var a *anypb.Any
		a, _ = anypb.New(wrapperspb.Bool(x.(bool)))
		return a
	default:
		log.Error(errors.New("convertToAnypb() unknown type"), "v =", v, "x =", x)
		return nil
	}
	return nil
}

func convertToString(x any) string {
	switch v := x.(type) {
	case int64:
		return fmt.Sprintf("%d", x.(int64))
	case string:
		return x.(string)
	case int:
		return fmt.Sprintf("%d", x.(int))
	case uint:
		return fmt.Sprintf("%d", x.(uint))
	case bool:
		if x.(bool) {
			return "true"
		}
		return "false"
	default:
		log.Info("convertToSTring() unknown type", v)
		log.Error(errors.New("convertToSTring() unknown type"), "v =", v, "x =", x)
		return ""
	}
	return ""
}

// Wrapping the int into a protobuf message
func (d *Droplet) NewChangeEvent(fname string, origval any, newval any) *Event {
	var e *Event
	e = new(Event)

	e.Droplet = d.Hostname
	e.OrigVal = convertToString(origval)
	e.NewVal = convertToString(newval)
	e.FieldName = fname

	now := time.Now()
	e.Start = timestamppb.New(now)

	// this also works, but it's a bit overkill
	// e.NewAny = convertToAnypb(newval)

	// me.events.Events = append(me.events.Events, e)
	// stuff := me.events.FormatJSON()
	// log.Info("events:", stuff)
	return e
}

// work in progress
func NewAddEvent(a any, fname string, newval any) *Event {
	var e *Event
	e = new(Event)

	switch v := a.(type) {
	case *Droplet:
		var d *Droplet
		d = a.(*Droplet)
		e.Droplet = d.Hostname
	case *Cluster:
		e.Droplet = "Cluster"
	case nil:
		e.Droplet = "<nil>"
	default:
		log.Info("newAddEvent() unknown type", v)
		e.Droplet = "on something somewhere"
	}

	e.NewVal = convertToString(newval)
	e.FieldName = fname

	now := time.Now()
	e.Start = timestamppb.New(now)

	return e
}

// update the droplet memory
func (d *Droplet) SetMemory(b int64) *Event {
	oldm := HumanFormatBytes(d.Memory)
	newm := HumanFormatBytes(b)
	if d.Memory == b {
		// log.Info("droplet", d.Hostname, "memory unchanged", oldm, "to", newm)
		return nil
	}
	log.Info("droplet", d.Hostname, "memory change from", oldm, "to", newm)

	return d.NewChangeEvent("Droplet.Memory", d.Memory, b)
}

// update the droplet memory
func (d *Droplet) SetCpus(b int64) {
	log.Info("Set the number of cpus for the droplet", b)
}

// records an event that the droplet changed state (aka turned on, turned off, etc)
func (c *Cluster) ChangeDropletState(d *Droplet, newState DropletState) error {
	if c == nil {
		return errors.New("cluster is nil")
	}
	if d == nil {
		return errors.New("droplet is nil")
	}
	if d.Current.State == newState {
		// droplet status didn't change
		return nil
	}
	var e *Event
	e = new(Event)

	e.Droplet = d.Hostname
	e.OrigVal = convertToString(d.Current.State)
	e.NewVal = convertToString(newState)
	e.FieldName = "status"

	now := time.Now()
	e.Start = timestamppb.New(now)

	c.E.Events = append(c.E.Events, e)
	return nil
}

// records an event that the droplet migrated to another hypervisor
func (c *Cluster) DropletMoved(d *Droplet, newh *Hypervisor) error {
	if c == nil {
		return errors.New("cluster is nil")
	}
	if d == nil {
		return errors.New("droplet is nil")
	}
	if newh == nil {
		return errors.New("hypervisor is nil")
	}
	if d.Current.Hypervisor == newh.Hostname {
		// droplet didn't move
		return nil
	}

	// make a change event
	var e *Event
	e = new(Event)

	e.Droplet = d.Hostname
	e.OrigVal = d.Current.Hypervisor
	e.NewVal = newh.Hostname
	e.FieldName = "droplet migrate"

	now := time.Now()
	e.Start = timestamppb.New(now)

	c.E.Events = append(c.E.Events, e)

	// update the droplet record
	d.Current.Hypervisor = newh.Hostname
	return nil
}
