package deploy

import (
	"reflect"
	"sort"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/psviderski/uncloud/pkg/api"
)

type ContainerSpecStatus string

const (
	ContainerUpToDate      ContainerSpecStatus = "up-to-date"
	ContainerNeedsUpdate   ContainerSpecStatus = "needs-update"
	ContainerNeedsRecreate ContainerSpecStatus = "needs-recreate"
)

func EvalContainerSpecChange(current api.ServiceSpec, new api.ServiceSpec) ContainerSpecStatus {
	current = current.SetDefaults()
	new = new.SetDefaults()

	if current.Mode != new.Mode {
		return ContainerNeedsRecreate
	}
	if current.Name != new.Name {
		return ContainerNeedsRecreate
	}

	// If pull policy is set to always, the container needs to be recreated.
	if new.Container.PullPolicy == api.PullPolicyAlways {
		return ContainerNeedsRecreate
	}
	new.Container.PullPolicy = current.Container.PullPolicy

	// Save mutable container resources that can be updated without recreation.
	newResources := new.Container.Resources
	// Temporarily set mutable container resources to current values to check if other properties changed.
	new.Container.Resources = current.Container.Resources

	// Check if immutable container properties changed.
	if !current.Container.Equals(new.Container) {
		return ContainerNeedsRecreate
	}

	if !cmp.Equal(current.Placement, new.Placement, cmpopts.EquateEmpty()) {
		// TODO: this could be just an in-place spec update when available.
		return ContainerNeedsRecreate
	}

	// TODO: change ports check to ContainerNeedsUpdate when ingress ports are stored only in the machine DB instead
	//  of as labels ans synced to the cluster store. Host ports changes should be handled as ContainerNeedsRecreate.
	if !api.PortsEqual(current.Ports, new.Ports) {
		return ContainerNeedsRecreate
	}

	// Compare volumes.
	if len(current.Volumes) != len(new.Volumes) {
		return ContainerNeedsRecreate
	}
	sortVolumes(current.Volumes)
	sortVolumes(new.Volumes)
	for i := range current.Volumes {
		if !current.Volumes[i].Equals(new.Volumes[i]) {
			// TODO: require only spec update for cases (see TODOs in tests):
			//  * bind volumes with different CreateHostPath
			//  * changed reference name in spec but preserved original volume name
			// TODO: should defined but not used (no corresponding mounts) volumes be simply ignored?
			return ContainerNeedsRecreate
		}
	}

	// Check if any mutable properties changed.
	if !current.Caddy.Equals(new.Caddy) {
		return ContainerNeedsRecreate
	}

	if !reflect.DeepEqual(current.Container.Resources, newResources) {
		return ContainerNeedsUpdate
	}

	return ContainerUpToDate
}

func sortVolumes(volumes []api.VolumeSpec) {
	sort.Slice(volumes, func(i, j int) bool {
		return volumes[i].Name < volumes[j].Name
	})
}
