package wait

import (
	"context"
	"errors"
	"fmt"
	"reflect"
	"time"

	infra "github.com/tidbcloud/tidb-management-service/api/serverless_infra"
	infraclient "github.com/tidbcloud/tidb-management-service/api/serverless_infra/client"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/util/wait"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

var Poll = time.Second * 10

func WaitForObjectList(
	ctx context.Context,
	c client.Client,
	objList client.ObjectList,
	listOptions *client.ListOptions,
	cond func() bool,
	timeout time.Duration,
) error {
	if err := wait.PollUntilContextTimeout(ctx, Poll, timeout, true, func(ctx context.Context) (bool, error) {
		if err := c.List(ctx, objList, listOptions); err != nil {
			return false, fmt.Errorf("can't list %v: %w", reflect.TypeOf(objList), err)
		}

		if cond() {
			return true, nil
		}

		return false, nil
	}); err != nil {
		if wait.Interrupted(err) {
			return fmt.Errorf("wait for list %v timeout: %w", reflect.TypeOf(objList), err)
		}

		return fmt.Errorf("can't wait for list %v, error : %w", reflect.TypeOf(objList), err)
	}

	return nil
}

func WaitForCondition(
	ctx context.Context,
	c client.Client,
	obj client.Object,
	cond func() bool,
	timeout time.Duration,
) error {
	if err := wait.PollUntilContextTimeout(ctx, Poll, timeout, true, func(ctx context.Context) (bool, error) {
		key := client.ObjectKeyFromObject(obj)
		if err := c.Get(ctx, key, obj); err != nil {
			if apierrors.IsNotFound(err) {
				return false, nil
			}

			return false, fmt.Errorf("can't get obj %s: %w", key, err)
		}

		if cond() {
			return true, nil
		}

		return false, nil
	}); err != nil {
		if wait.Interrupted(err) {
			return fmt.Errorf("wait for object %v condition timeout: %w", obj, err)
		}

		return fmt.Errorf("can't wait for object %v condition, error : %w", obj, err)
	}

	return nil
}

func WaitForObjectDeleted(
	ctx context.Context,
	c client.Client,
	obj client.Object,
	timeout time.Duration,
) error {
	if err := wait.PollUntilContextTimeout(ctx, Poll, timeout, true, func(ctx context.Context) (bool, error) {
		key := client.ObjectKeyFromObject(obj)
		if err := c.Get(ctx, key, obj); err != nil {
			if apierrors.IsNotFound(err) {
				return true, nil
			}

			return false, fmt.Errorf("can't get object %s: %w", key, err)
		}

		return false, nil
	}); err != nil {
		if wait.Interrupted(err) {
			return fmt.Errorf("wait for object %v deleted timeout: %w", obj, err)
		}

		return fmt.Errorf("can't wait for object %v deleted, error : %w", obj, err)
	}

	return nil
}

func WaitForInfraClusterCondition(
	ctx context.Context,
	c infraclient.Client,
	cluster *infra.Cluster,
	cond func() bool,
	timeout time.Duration,
) error {
	if err := wait.PollUntilContextTimeout(ctx, Poll, timeout, true, func(ctx context.Context) (bool, error) {
		if err := c.Get(ctx, cluster); err != nil {
			if errors.Is(err, infraclient.ErrNotFound) {
				return false, nil
			}

			return false, fmt.Errorf("can't get obj %s/%s: %w", cluster.Namespace, cluster.Name, err)
		}

		if cond() {
			return true, nil
		}

		return false, nil
	}); err != nil {
		if wait.Interrupted(err) {
			return fmt.Errorf("wait for object %#v condition timeout: %w", cluster, err)
		}

		return fmt.Errorf("can't wait for object %#v condition, error : %w", cluster, err)
	}

	return nil
}
