package wait

import (
	"context"
	"fmt"
	"time"

	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/util/wait"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

var (
	Poll = time.Second * 2

	ShortTaskTimeout = time.Minute * 3
	LongTaskTimeout  = time.Minute * 120
)

func WaitForCondition(
	ctx context.Context,
	c client.Client,
	obj client.Object,
	cond func() error,
	timeout time.Duration,
) error {
	var lastErr 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 err := cond(); err != nil {
			lastErr = err
			return false, nil
		}

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

		return fmt.Errorf("can't wait for object %T(%v) condition, error : %w", obj, client.ObjectKeyFromObject(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 WaitForAllObjectInNamespaceDeleted(
	ctx context.Context,
	c client.Client,
	ns string,
	list client.ObjectList,
	timeout time.Duration,
) error {
	if err := wait.PollUntilContextTimeout(ctx, Poll, timeout, true, func(ctx context.Context) (bool, error) {
		if err := c.List(ctx, list, client.InNamespace(ns)); err != nil {
			return false, fmt.Errorf("can't list object %T in %s: %w", list, ns, err)
		}

		if meta.LenList(list) == 0 {
			return true, nil
		}

		return false, nil
	}); err != nil {
		if wait.Interrupted(err) {
			return fmt.Errorf("wait for list %T in %s deleted timeout: %w", list, ns, err)
		}

		return fmt.Errorf("can't wait for list %T in %s deleted, error : %w", list, ns, err)
	}

	return nil
}
