package tilt

import (
	"encoding/json"
	"fmt"
	"strings"
)

// ResourceReadiness summarizes Tilt UIResource readiness at one point in time.
type ResourceReadiness struct {
	ReadyResources    int
	TotalResources    int
	NotReadyResources []string
	ResourceReady     map[string]bool
	ResourceStates    map[string]ResourceState
}

// ResourceState preserves the current Tilt state, the most recent Ready
// transition, and the latest build identity so deploy waits can distinguish a
// new update from an unchanged terminal state.
type ResourceState struct {
	RuntimeStatus      string
	UpdateStatus       string
	TriggerMode        int
	ImageStamp         string
	ReadyStatus        string
	Reason             string
	Message            string
	LastTransitionTime string
	BuildHistoryCount  int
	LatestBuildSpanID  string
	LatestBuildStart   string
}

type uiResourceList struct {
	Items []uiResource `json:"items"`
}

type uiResource struct {
	Metadata uiResourceMetadata `json:"metadata"`
	Status   uiResourceStatus   `json:"status"`
}

type uiResourceMetadata struct {
	Name        string            `json:"name"`
	Annotations map[string]string `json:"annotations"`
}

type uiResourceStatus struct {
	Conditions    []uiResourceCondition `json:"conditions"`
	RuntimeStatus string                `json:"runtimeStatus"`
	UpdateStatus  string                `json:"updateStatus"`
	TriggerMode   int                   `json:"triggerMode"`
	BuildHistory  []uiResourceBuild     `json:"buildHistory"`
}

type uiResourceBuild struct {
	SpanID    string `json:"spanID"`
	StartTime string `json:"startTime"`
}

type uiResourceCondition struct {
	Type               string `json:"type"`
	Status             string `json:"status"`
	Reason             string `json:"reason"`
	Message            string `json:"message"`
	LastTransitionTime string `json:"lastTransitionTime"`
}

// ParseUIResourceReadiness converts `tilt get uiresources -o json` output into
// the compact readiness counters persisted in tilt-status.json.
func ParseUIResourceReadiness(data []byte) (*ResourceReadiness, error) {
	var list uiResourceList
	if err := json.Unmarshal(data, &list); err != nil {
		return nil, fmt.Errorf("parse Tilt UIResource list: %w", err)
	}
	out := &ResourceReadiness{
		TotalResources: len(list.Items),
		ResourceReady:  make(map[string]bool, len(list.Items)),
		ResourceStates: make(map[string]ResourceState, len(list.Items)),
	}
	for i := range list.Items {
		item := &list.Items[i]
		name := strings.TrimSpace(item.Metadata.Name)
		if name == "" {
			name = fmt.Sprintf("uiresource[%d]", i)
		}
		out.ResourceStates[name] = uiResourceState(item)
		if uiResourceReady(item) {
			out.ReadyResources++
			out.ResourceReady[name] = true
			continue
		}
		out.ResourceReady[name] = false
		out.NotReadyResources = append(out.NotReadyResources, name)
	}
	return out, nil
}

func uiResourceState(item *uiResource) ResourceState {
	if item == nil {
		return ResourceState{}
	}
	state := ResourceState{
		RuntimeStatus:     strings.TrimSpace(item.Status.RuntimeStatus),
		UpdateStatus:      strings.TrimSpace(item.Status.UpdateStatus),
		TriggerMode:       item.Status.TriggerMode,
		ImageStamp:        strings.TrimSpace(item.Metadata.Annotations["cp9.tidbcloud.com/image-stamp"]),
		BuildHistoryCount: len(item.Status.BuildHistory),
	}
	if len(item.Status.BuildHistory) > 0 {
		build := item.Status.BuildHistory[0]
		state.LatestBuildSpanID = strings.TrimSpace(build.SpanID)
		state.LatestBuildStart = strings.TrimSpace(build.StartTime)
	}
	for _, condition := range item.Status.Conditions {
		if !strings.EqualFold(strings.TrimSpace(condition.Type), "Ready") {
			continue
		}
		state.ReadyStatus = strings.TrimSpace(condition.Status)
		state.Reason = strings.TrimSpace(condition.Reason)
		state.Message = strings.TrimSpace(condition.Message)
		state.LastTransitionTime = strings.TrimSpace(condition.LastTransitionTime)
		break
	}
	return state
}

func uiResourceReady(item *uiResource) bool {
	if item == nil {
		return false
	}
	if strings.HasSuffix(strings.TrimSpace(item.Metadata.Name), "-live-binary") &&
		strings.EqualFold(strings.TrimSpace(item.Status.RuntimeStatus), "not_applicable") &&
		strings.EqualFold(strings.TrimSpace(item.Status.UpdateStatus), "none") {
		return true
	}
	for _, condition := range item.Status.Conditions {
		if strings.EqualFold(strings.TrimSpace(condition.Type), "Ready") {
			return strings.EqualFold(strings.TrimSpace(condition.Status), "True")
		}
	}
	runtimeStatus := strings.TrimSpace(item.Status.RuntimeStatus)
	updateStatus := strings.TrimSpace(item.Status.UpdateStatus)
	return strings.EqualFold(runtimeStatus, "ok") && strings.EqualFold(updateStatus, "ok")
}
