package summary

import (
	"strings"
	"time"

	"github.com/rancher/wrangler/pkg/data"
	"github.com/rancher/wrangler/pkg/data/convert"
	"github.com/rancher/wrangler/pkg/kv"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
)

const (
	kindSep = ", Kind="
)

var (
	// True ==
	// False == error
	// Unknown == transitioning
	TransitioningUnknown = map[string]string{
		"Active":                      "activating",
		"AddonDeploy":                 "provisioning",
		"AgentDeployed":               "provisioning",
		"BackingNamespaceCreated":     "configuring",
		"Built":                       "building",
		"CertsGenerated":              "provisioning",
		"ConfigOK":                    "configuring",
		"Created":                     "creating",
		"CreatorMadeOwner":            "configuring",
		"DefaultNamespaceAssigned":    "configuring",
		"DefaultNetworkPolicyCreated": "configuring",
		"DefaultProjectCreated":       "configuring",
		"DockerProvisioned":           "provisioning",
		"Deployed":                    "deploying",
		"Drained":                     "draining",
		"Downloaded":                  "downloading",
		"etcd":                        "provisioning",
		"Inactive":                    "deactivating",
		"Initialized":                 "initializing",
		"Installed":                   "installing",
		"NodesCreated":                "provisioning",
		"Pending":                     "pending",
		"PodScheduled":                "scheduling",
		"Provisioned":                 "provisioning",
		"Refreshed":                   "refreshed",
		"Registered":                  "registering",
		"Removed":                     "removing",
		"Saved":                       "saving",
		"Updated":                     "updating",
		"Updating":                    "updating",
		"Upgraded":                    "upgrading",
		"Waiting":                     "waiting",
		"InitialRolesPopulated":       "activating",
		"ScalingActive":               "pending",
		"AbleToScale":                 "pending",
		"RunCompleted":                "running",
		"Processed":                   "processed",
	}

	// True == error
	// False ==
	// Unknown ==
	ErrorTrue = map[string]bool{
		"OutOfDisk":           true,
		"MemoryPressure":      true,
		"DiskPressure":        true,
		"NetworkUnavailable":  true,
		"KernelHasNoDeadlock": true,
		"Unschedulable":       true,
		"ReplicaFailure":      true,
	}

	// True ==
	// False == error
	// Unknown ==
	ErrorFalse = map[string]bool{
		"Failed":      true,
		"Progressing": true,
	}

	// True ==
	// False == transitioning
	// Unknown == error
	TransitioningFalse = map[string]string{
		"Completed":   "activating",
		"Ready":       "unavailable",
		"Available":   "updating",
		"Progressing": "inactive",
	}

	Summarizers []Summarizer
)

type Summarizer func(obj data.Object, conditions []Condition, summary Summary) Summary

func init() {
	Summarizers = []Summarizer{
		checkStatusSummary,
		checkErrors,
		checkTransitioning,
		checkActive,
		checkPhase,
		checkInitializing,
		checkRemoving,
		checkStandard,
		checkLoadBalancer,
		checkPod,
		checkPodSelector,
		checkOwner,
		checkApplyOwned,
		checkCattleTypes,
	}
}

func checkOwner(obj data.Object, conditions []Condition, summary Summary) Summary {
	ustr := &unstructured.Unstructured{
		Object: obj,
	}
	for _, ownerref := range ustr.GetOwnerReferences() {
		rel := Relationship{
			Name:       ownerref.Name,
			Kind:       ownerref.Kind,
			APIVersion: ownerref.APIVersion,
			Type:       "owner",
			Inbound:    true,
		}
		if ownerref.Controller != nil && *ownerref.Controller {
			rel.ControlledBy = true
		}

		summary.Relationships = append(summary.Relationships, rel)
	}

	return summary
}

func checkStatusSummary(obj data.Object, conditions []Condition, summary Summary) Summary {
	summaryObj := obj.Map("status", "display")
	if len(summaryObj) == 0 {
		summaryObj = obj.Map("status", "summary")
		if len(summaryObj) == 0 {
			return summary
		}
	}
	obj = summaryObj

	if _, ok := obj["state"]; ok {
		summary.State = obj.String("state")
	}
	if _, ok := obj["transitioning"]; ok {
		summary.Transitioning = obj.Bool("transitioning")
	}
	if _, ok := obj["error"]; ok {
		summary.Error = obj.Bool("error")
	}
	if _, ok := obj["message"]; ok {
		summary.Message = append(summary.Message, obj.String("message"))
	}

	return summary
}

func checkStandard(obj data.Object, conditions []Condition, summary Summary) Summary {
	if summary.State != "" {
		return summary
	}

	// this is a hack to not call the standard summarizers on norman mapped objects
	if strings.HasPrefix(obj.String("type"), "/") {
		return summary
	}

	result, err := kstatus.Compute(&unstructured.Unstructured{Object: obj})
	if err != nil {
		return summary
	}

	switch result.Status {
	case kstatus.InProgressStatus:
		summary.State = "in-progress"
		summary.Message = append(summary.Message, result.Message)
		summary.Transitioning = true
	case kstatus.FailedStatus:
		summary.State = "failed"
		summary.Message = append(summary.Message, result.Message)
		summary.Error = true
	case kstatus.CurrentStatus:
		summary.State = "active"
		summary.Message = append(summary.Message, result.Message)
	case kstatus.TerminatingStatus:
		summary.State = "removing"
		summary.Message = append(summary.Message, result.Message)
		summary.Transitioning = true
	}

	return summary
}

func checkErrors(_ data.Object, conditions []Condition, summary Summary) Summary {
	for _, c := range conditions {
		if (ErrorFalse[c.Type()] && c.Status() == "False") || c.Reason() == "Error" {
			summary.Error = true
			summary.Message = append(summary.Message, c.Message())
			if summary.State == "active" || summary.State == "" {
				summary.State = "error"
			}
			break
		}
	}

	if summary.Error {
		return summary
	}

	for _, c := range conditions {
		if ErrorTrue[c.Type()] && c.Status() == "True" {
			summary.Error = true
			summary.Message = append(summary.Message, c.Message())
		}
	}
	return summary
}

func checkTransitioning(_ data.Object, conditions []Condition, summary Summary) Summary {
	for _, c := range conditions {
		newState, ok := TransitioningUnknown[c.Type()]
		if !ok {
			continue
		}

		if c.Status() == "False" {
			summary.Error = true
			summary.State = newState
			summary.Message = append(summary.Message, c.Message())
		} else if c.Status() == "Unknown" && summary.State == "" {
			summary.Transitioning = true
			summary.State = newState
			summary.Message = append(summary.Message, c.Message())
		}
	}

	for _, c := range conditions {
		if summary.State != "" {
			break
		}
		newState, ok := TransitioningFalse[c.Type()]
		if !ok {
			continue
		}
		if c.Status() == "False" {
			summary.Transitioning = true
			summary.State = newState
			summary.Message = append(summary.Message, c.Message())
		} else if c.Status() == "Unknown" {
			summary.Error = true
			summary.State = newState
			summary.Message = append(summary.Message, c.Message())
		}
	}

	return summary
}

func checkActive(obj data.Object, _ []Condition, summary Summary) Summary {
	if summary.State != "" {
		return summary
	}

	switch obj.String("spec", "active") {
	case "true":
		summary.State = "active"
	case "false":
		summary.State = "inactive"
	}

	return summary
}

func checkPhase(obj data.Object, _ []Condition, summary Summary) Summary {
	phase := obj.String("status", "phase")
	if phase == "Succeeded" {
		summary.State = "succeeded"
		summary.Transitioning = false
	} else if phase != "" && summary.State == "" {
		summary.State = phase
	}
	return summary
}

func checkInitializing(obj data.Object, conditions []Condition, summary Summary) Summary {
	apiVersion := obj.String("apiVersion")
	_, hasConditions := obj.Map("status")["conditions"]
	if summary.State == "" && hasConditions && len(conditions) == 0 && strings.Contains(apiVersion, "cattle.io") {
		val := obj.String("metadata", "created")
		if i, err := convert.ToTimestamp(val); err == nil {
			if time.Unix(i/1000, 0).Add(5 * time.Second).After(time.Now()) {
				summary.State = "initializing"
				summary.Transitioning = true
			}
		}
	}
	return summary
}

func checkRemoving(obj data.Object, conditions []Condition, summary Summary) Summary {
	removed := obj.String("metadata", "removed")
	if removed == "" {
		return summary
	}

	summary.State = "removing"
	summary.Transitioning = true

	finalizers := obj.StringSlice("metadata", "finalizers")
	if len(finalizers) == 0 {
		finalizers = obj.StringSlice("spec", "finalizers")
	}

	for _, cond := range conditions {
		if cond.Type() == "Removed" && (cond.Status() == "Unknown" || cond.Status() == "False") && cond.Message() != "" {
			summary.Message = append(summary.Message, cond.Message())
		}
	}

	if len(finalizers) == 0 {
		return summary
	}

	_, f := kv.RSplit(finalizers[0], "controller.cattle.io/")
	if f == "foregroundDeletion" {
		f = "object cleanup"
	}

	summary.Message = append(summary.Message, "waiting on "+f)
	if i, err := convert.ToTimestamp(removed); err == nil {
		if time.Unix(i/1000, 0).Add(5 * time.Minute).Before(time.Now()) {
			summary.Error = true
		}
	}

	return summary
}

func checkLoadBalancer(obj data.Object, _ []Condition, summary Summary) Summary {
	if (summary.State == "active" || summary.State == "") &&
		obj.String("kind") == "Service" &&
		(obj.String("spec", "serviceKind") == "LoadBalancer" ||
			obj.String("spec", "type") == "LoadBalancer") {
		addresses := obj.Slice("status", "loadBalancer", "ingress")
		if len(addresses) == 0 {
			summary.State = "pending"
			summary.Transitioning = true
			summary.Message = append(summary.Message, "Load balancer is being provisioned")
		}
	}

	return summary
}

func isKind(obj data.Object, kind string, apiGroups ...string) bool {
	if obj.String("kind") != kind {
		return false
	}

	if len(apiGroups) == 0 {
		return obj.String("apiVersion") == "v1"
	}

	if len(apiGroups) == 0 {
		apiGroups = []string{""}
	}

	for _, group := range apiGroups {
		switch {
		case group == "":
			if obj.String("apiVersion") == "v1" {
				return true
			}
		case group[len(group)-1] == '/':
			if strings.HasPrefix(obj.String("apiVersion"), group) {
				return true
			}
		default:
			if obj.String("apiVersion") != group {
				return true
			}
		}
	}

	return false
}

func checkApplyOwned(obj data.Object, conditions []Condition, summary Summary) Summary {
	if len(obj.Slice("metadata", "ownerReferences")) > 0 {
		return summary
	}

	annotations := obj.Map("metadata", "annotations")
	gvkString := convert.ToString(annotations["objectset.rio.cattle.io/owner-gvk"])
	i := strings.Index(gvkString, kindSep)
	if i <= 0 {
		return summary
	}

	name := convert.ToString(annotations["objectset.rio.cattle.io/owner-name"])
	namespace := convert.ToString(annotations["objectset.rio.cattle.io/owner-namespace"])

	apiVersion := gvkString[:i]
	kind := gvkString[i+len(kindSep):]

	rel := Relationship{
		Name:       name,
		Namespace:  namespace,
		Kind:       kind,
		APIVersion: apiVersion,
		Type:       "applies",
		Inbound:    true,
	}

	summary.Relationships = append(summary.Relationships, rel)

	return summary
}
