package tioperator

import (
	"context"
	"fmt"
	"strings"
	"time"

	v1alpha1br "github.com/pingcap/tidb-operator/api/v2/br/v1alpha1"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const DefaultTiBRName = "tibr-default"
const DefaultTiBRGCRName = "tibrgc-default"

func TiBRCRNameOfRestore(restoreId string) string {
	return fmt.Sprintf("restore-%s", restoreId)
}

func TiBRCRNameOfManualBackup(keyspace, manualBackupId string) string {
	return strings.ToLower(fmt.Sprintf("mb-%s-%s", keyspace, manualBackupId))
}

func TiBRCRNameOfManualRestore(restoreId string) string {
	return fmt.Sprintf("mr-%s", restoreId)
}

func ClassicRestoreCRName(restoreId string) string {
	return fmt.Sprintf("cl-restore-%s", restoreId)
}

func IsTiBRSyncedReady(tibrCR *v1alpha1br.TiBR) bool {
	if tibrCR == nil {
		return false
	}

	ready, synced := false, false
	for _, c := range tibrCR.Status.Conditions {
		switch c.Type {
		case "Ready":
			ready = c.Status == metav1.ConditionTrue
		case "Synced":
			synced = c.Status == metav1.ConditionTrue
		}
	}
	if !ready || !synced {
		return false
	}
	return true
}

func GetBackupComponentsStatus(ctx context.Context, remoteCli client.Client, dataplaneNs string) (apiv1.Component_Phase, error) {
	tibr := &v1alpha1br.TiBR{}
	err := remoteCli.Get(ctx, types.NamespacedName{
		Name:      DefaultTiBRName,
		Namespace: dataplaneNs,
	}, tibr)
	if err != nil {
		if apierrors.IsNotFound(err) {
			tilog.Warn(ctx, "tibr is not found",
				zap.String("name", DefaultTiBRName),
				zap.String("namespace", dataplaneNs),
				zap.String("backup_component_failure_reason", "tibr_not_found"))
			return apiv1.Component_UNAVAILABLE, nil
		}
		return apiv1.Component_UNAVAILABLE, fmt.Errorf("failed to get tibr: %w", err)
	}
	if !IsTiBRSyncedReady(tibr) {
		ready := meta.FindStatusCondition(tibr.Status.Conditions, v1alpha1br.TiBRCondReady)
		synced := meta.FindStatusCondition(tibr.Status.Conditions, v1alpha1br.TiBRCondSynced)
		tilog.Warn(ctx, "tibr is not ready or synced",
			zap.String("name", tibr.GetName()),
			zap.String("namespace", tibr.GetNamespace()),
			zap.Int64("generation", tibr.GetGeneration()),
			zap.Int64("observed_generation", tibr.Status.ObservedGeneration),
			zap.String("current_revision", tibr.Status.CurrentRevision),
			zap.String("update_revision", tibr.Status.UpdateRevision),
			zap.String("ready_status", conditionStatus(ready)),
			zap.String("ready_reason", conditionReason(ready)),
			zap.String("ready_message", conditionMessage(ready)),
			zap.String("ready_last_transition_time", conditionLastTransitionTime(ready)),
			zap.String("synced_status", conditionStatus(synced)),
			zap.String("synced_reason", conditionReason(synced)),
			zap.String("synced_message", conditionMessage(synced)),
			zap.String("synced_last_transition_time", conditionLastTransitionTime(synced)),
			zap.Any("conditions", tibr.Status.Conditions),
			zap.String("backup_component_failure_reason", "tibr_not_ready_or_synced"))
		return apiv1.Component_UNAVAILABLE, nil
	}

	tibrgc := &v1alpha1br.TiBRGC{}
	err = remoteCli.Get(ctx, types.NamespacedName{
		Name:      DefaultTiBRGCRName,
		Namespace: dataplaneNs,
	}, tibrgc)
	if err != nil {
		if apierrors.IsNotFound(err) {
			tilog.Warn(ctx, "tibrgc is not found",
				zap.String("name", DefaultTiBRGCRName),
				zap.String("namespace", dataplaneNs),
				zap.String("backup_component_failure_reason", "tibrgc_not_found"))
			return apiv1.Component_UNAVAILABLE, nil
		}
		return apiv1.Component_UNAVAILABLE, fmt.Errorf("failed to get tibrgc: %w", err)
	}
	return apiv1.Component_AVAILABLE, nil
}

func conditionStatus(condition *metav1.Condition) string {
	if condition == nil {
		return "Missing"
	}
	return string(condition.Status)
}

func conditionReason(condition *metav1.Condition) string {
	if condition == nil {
		return ""
	}
	return condition.Reason
}

func conditionMessage(condition *metav1.Condition) string {
	if condition == nil {
		return ""
	}
	return condition.Message
}

func conditionLastTransitionTime(condition *metav1.Condition) string {
	if condition == nil || condition.LastTransitionTime.IsZero() {
		return ""
	}
	return condition.LastTransitionTime.UTC().Format(time.RFC3339Nano)
}
