package task

import (
	"context"
	"time"

	"k8s.io/apimachinery/pkg/api/meta"
	"sigs.k8s.io/controller-runtime/pkg/client"

	chaosmesh "github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
	"github.com/tidbcloud/eks-provider/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/helm"
	"github.com/tidbcloud/infra-provider/pkg/k8s"

	"github.com/tidbcloud/chaos-provider/pkg/config"
)

type ChaosMesh struct {
	BaseTask
	config *config.ChaosMeshConfig
}

// NewChaosMesh installs and uninstalls the Chaos Mesh from Helm charts.
func NewChaosMesh(ctx *Context) *ChaosMesh {
	return &ChaosMesh{
		BaseTask: BaseTask{Context: ctx},
		config:   &ctx.Config.ChaosMesh,
	}
}

func (c *ChaosMesh) Name() string {
	return "Chaos Mesh"
}

func (c *ChaosMesh) Create(ctx context.Context) deploy.Result {
	return deploy.ErrorOrDone(helm.Apply(ctx, c.Remote, c.config))
}

func (c *ChaosMesh) Delete(ctx context.Context) deploy.Result {
	if err := c.cleanChaosWorkflows(ctx); err != nil {
		return deploy.Error(err)
	}

	isEmpty, err := c.checkChaosWorkflowsIsEmpty(ctx)
	if err != nil {
		return deploy.Error(err)
	}

	if !isEmpty {
		c.Logger.Info("ChaosWorkflow is not empty, requeue after 2 seconds")
		return deploy.Panic(nil).WithRequeuAfter(2 * time.Second)
	}

	if err := c.cleanChaosMeshWorkflow(ctx); err != nil {
		return deploy.Error(err)
	}

	workflowIsEmpty, err := c.checkChaosMeshWorkflowIsEmpty(ctx)
	if err != nil {
		return deploy.Error(err)
	}

	if !workflowIsEmpty {
		c.Logger.Info("Chaos Mesh Workflow is not empty, requeue after 2 seconds")
		return deploy.Panic(nil).WithRequeuAfter(2 * time.Second)
	}

	return deploy.ErrorOrDone(helm.Uninstall(ctx, c.Remote, c.config, c.config.GetName()))
}

func (c *ChaosMesh) cleanChaosWorkflows(ctx context.Context) error {
	chaosWorkflows, err := c.listChaosWorkflow(ctx)
	if err != nil {
		return err
	}

	for _, cw := range chaosWorkflows.Items {
		if err := k8s.DeleteObject(ctx, c.Local, &cw); err != nil {
			return err
		}
	}

	return nil
}

func (c *ChaosMesh) checkChaosWorkflowsIsEmpty(ctx context.Context) (bool, error) {
	chaosWorkflows, err := c.listChaosWorkflow(ctx)
	if err != nil {
		return false, err
	}

	if len(chaosWorkflows.Items) > 0 {
		return false, nil
	}

	return true, nil
}

func (c *ChaosMesh) listChaosWorkflow(ctx context.Context) (*v1alpha1.ChaosWorkflowList, error) {
	chaosWorkflows := &v1alpha1.ChaosWorkflowList{}
	if err := c.Local.List(ctx, chaosWorkflows, &client.ListOptions{
		Namespace: c.ChaosServer.Namespace,
	}); err != nil {
		return nil, err
	}

	result := &v1alpha1.ChaosWorkflowList{}
	for _, cw := range chaosWorkflows.Items {
		if cw.Spec.NetworkName == c.ChaosServer.Spec.NetworkName {
			result.Items = append(result.Items, cw)
		}
	}

	return result, nil
}

func (c *ChaosMesh) cleanChaosMeshWorkflow(ctx context.Context) error {
	cli, err := c.Remote.GetClient(ctx)
	if err != nil {
		return err
	}

	workflows := &chaosmesh.WorkflowList{}
	if err := cli.List(ctx, workflows); err != nil {
		if meta.IsNoMatchError(err) {
			return nil
		}
		return err
	}

	for _, wf := range workflows.Items {
		if err := k8s.DeleteObject(ctx, cli, &wf); err != nil {
			return err
		}
	}

	return nil
}

func (c *ChaosMesh) checkChaosMeshWorkflowIsEmpty(ctx context.Context) (bool, error) {
	cli, err := c.Remote.GetClient(ctx)
	if err != nil {
		return false, err
	}

	workflows := &chaosmesh.WorkflowList{}
	if err := cli.List(ctx, workflows); err != nil {
		if meta.IsNoMatchError(err) {
			return true, nil
		}
		return false, err
	}

	if len(workflows.Items) > 0 {
		return false, nil
	}

	return true, nil
}
