/*
Copyright 2024.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package integrationtask

import (
	"context"
	"fmt"

	"github.com/go-logr/logr"
	batchv1 "k8s.io/api/batch/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/builder"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"
	"sigs.k8s.io/controller-runtime/pkg/predicate"

	o11yv1 "github.com/tidbcloud/observability/api/regionalserver/resourceworker/v1"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/deploy"
	"github.com/tidbcloud/observability/pkg/common/structs"
	"github.com/tidbcloud/observability/pkg/resourceworker/controllers/integrationtask/v2/task"
)

// IntegrationTaskReconciler reconciles an IntegrationTask object
type IntegrationTaskReconciler struct {
	client.Client
	Log         logr.Logger
	ServiceInfo structs.ServiceInfo
}

const (
	clusterDataPathTmpl = "%s/log_data/data/%s/%s/%s"
)

func NewIntegrationTaskReconciler(mgr ctrl.Manager, log logr.Logger, serviceInfo structs.ServiceInfo) *IntegrationTaskReconciler {
	return &IntegrationTaskReconciler{
		Client:      mgr.GetClient(),
		Log:         log,
		ServiceInfo: serviceInfo,
	}
}

func shouldWatchIntegrationTask(_ client.Object) bool {
	return true
}

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
func (r *IntegrationTaskReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	logger := log.FromContext(ctx)
	var integrationTask o11yv1.IntegrationTask
	logger.Info("Reconciling IntegrationTask")

	// 1. Get IntegrationTask CR from API server
	if err := r.Client.Get(ctx, req.NamespacedName, &integrationTask); err != nil {
		if apierrors.IsNotFound(err) {
			return ctrl.Result{}, nil
		}
		return ctrl.Result{}, fmt.Errorf("unable to get integration task %s: %v", req.NamespacedName, err)
	}

	if integrationTask.DeletionTimestamp.IsZero() {
		if _, ok := integrationTask.Annotations[constants.AnnotationKeyPauseReconcile]; ok {
			logger.Info("IntegrationTask has pause annotation, skipping reconciliation")
			return ctrl.Result{}, nil
		}
	}

	// 2. Build task Context
	c, err := (&task.Context{
		Task:        &integrationTask,
		Logger:      r.Log,
		Client:      r.Client,
		ServiceInfo: r.ServiceInfo,
	}).Default()
	if err != nil {
		return ctrl.Result{}, fmt.Errorf("unable to get task context: %v", err)
	}

	// 2.5 Fail fast on invalid identity, but only on the create/update path so an
	// already-invalid resource can still be deleted and cleaned up.
	if integrationTask.DeletionTimestamp.IsZero() {
		id, iderr := c.Identity()
		if iderr == nil {
			iderr = id.ValidateCreatable()
		}
		if iderr != nil {
			logger.Error(iderr, "invalid integration task identity")
			integrationTask.Status.Phase = constants.FAILED
			integrationTask.Status.LastError = iderr.Error()
			if uerr := update(ctx, r.Client, &integrationTask); uerr != nil {
				return ctrl.Result{}, uerr
			}
			return ctrl.Result{}, nil
		}
	}

	// 3. Build task list
	var tasks []deploy.Task = GetIntegrationTasks(r, c)

	// 4. Create deployer
	d := deploy.NewDeployer(tasks, nil, logger)

	// 5. Check deletion or creation
	var res ctrl.Result
	if integrationTask.DeletionTimestamp.IsZero() {
		// 6b. Deploy (create/update resources)
		res, err = updateCreate(ctx, c, d.Deploy(ctx))
	} else {
		// 7. Destroy (delete resources)
		res, err = updateDelete(ctx, c, d.Destroy(ctx))
	}

	if err == nil {
		logger.Info("ReconcileSuccess")
	} else {
		logger.Error(err, "ReconcileFail")
	}

	return res, err
}

func (r *IntegrationTaskReconciler) SetupWithManager(mgr ctrl.Manager) error {
	return ctrl.NewControllerManagedBy(mgr).
		Named("integrationtask-v2").
		For(
			&o11yv1.IntegrationTask{},
			builder.WithPredicates(predicate.NewPredicateFuncs(shouldWatchIntegrationTask)),
		).
		Owns(&batchv1.Job{}).
		Complete(r)
}
