package reconcilers

import (
	"context"
	"fmt"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/codes"
	"go.opentelemetry.io/otel/trace"

	"github.com/grafana/grafana-app-sdk/logging"
	"github.com/grafana/grafana-app-sdk/operator"
	foldersKind "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
	"github.com/grafana/grafana/pkg/apimachinery/utils"
	"github.com/grafana/grafana/pkg/services/authz"
)

// PermissionStore interface for managing folder permissions
type PermissionStore interface {
	GetFolderParents(ctx context.Context, namespace, folderUID string) ([]string, error)
	SetFolderParent(ctx context.Context, namespace, folderUID, parentUID string) error
	DeleteFolderParents(ctx context.Context, namespace, folderUID string) error
}

// ReconcilerConfig represents the app-specific configuration
type ReconcilerConfig struct {
	ZanzanaCfg authz.ZanzanaClientConfig
	Metrics    *ReconcilerMetrics
}

type FolderReconciler struct {
	permissionStore PermissionStore
	metrics         *ReconcilerMetrics
}

func NewFolderReconciler(cfg ReconcilerConfig, reg prometheus.Registerer) (operator.Reconciler, error) {
	// Create Zanzana client
	zanzanaClient, err := authz.NewRemoteZanzanaClient(cfg.ZanzanaCfg, reg)

	if err != nil {
		return nil, fmt.Errorf("unable to create zanzana client: %w", err)
	}

	// Create dependencies
	permissionStore := NewZanzanaPermissionStore(zanzanaClient)

	folderReconciler := &FolderReconciler{
		permissionStore: permissionStore,
		metrics:         cfg.Metrics,
	}

	reconciler := &operator.TypedReconciler[*foldersKind.Folder]{
		ReconcileFunc: folderReconciler.reconcile,
	}

	return reconciler, nil
}

// actionToString converts a ReconcileAction to a human-readable string
func actionToString(action operator.ReconcileAction) string {
	switch action {
	case operator.ReconcileActionCreated:
		return "create"
	case operator.ReconcileActionUpdated:
		return "update"
	case operator.ReconcileActionDeleted:
		return "delete"
	default:
		return "unknown"
	}
}

func (r *FolderReconciler) reconcile(ctx context.Context, req operator.TypedReconcileRequest[*foldersKind.Folder]) (operator.ReconcileResult, error) {
	// Create root span for the entire reconciliation process
	tracer := otel.GetTracerProvider().Tracer("iam-folder-reconciler")
	ctx, span := tracer.Start(ctx, "folder.reconcile",
		trace.WithAttributes(
			attribute.String("action", actionToString(req.Action)),
			attribute.String("folder.uid", req.Object.Name),
			attribute.String("namespace", req.Object.Namespace),
		),
	)
	defer span.End()

	// Add timeout to prevent hanging operations
	ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
	defer cancel()

	action := actionToString(req.Action)

	err := validateFolder(req.Object)
	if err != nil {
		span.RecordError(err)
		span.SetStatus(codes.Error, "validation failed")
		if r.metrics != nil {
			r.metrics.RecordReconcileFailure(action, "informer")
		}
		return operator.ReconcileResult{}, err
	}

	var result operator.ReconcileResult
	switch req.Action {
	case operator.ReconcileActionCreated:
		result, err = r.handleUpdateFolder(ctx, req.Object, action)
	case operator.ReconcileActionUpdated:
		result, err = r.handleUpdateFolder(ctx, req.Object, action)
	case operator.ReconcileActionDeleted:
		result, err = r.handleDeleteFolder(ctx, req.Object, action)
	default:
		if r.metrics != nil {
			r.metrics.RecordReconcileSuccess(action, "no_changes_needed")
		}
		return operator.ReconcileResult{}, nil
	}

	if err != nil {
		span.RecordError(err)
		span.SetStatus(codes.Error, "reconciliation failed")
	}

	return result, err
}

func (r *FolderReconciler) handleUpdateFolder(ctx context.Context, folder *foldersKind.Folder, action string) (operator.ReconcileResult, error) {
	logger := logging.FromContext(ctx)

	folderUID := folder.Name
	namespace := folder.Namespace

	parentUID, err := getFolderParent(ctx, folder)
	if err != nil {
		logger.Error("Error getting folder parent", "error", err)
		if r.metrics != nil {
			r.metrics.RecordReconcileFailure(action, "failure_informer")
		}
		return operator.ReconcileResult{}, err
	}

	parents, err := r.permissionStore.GetFolderParents(ctx, namespace, folderUID)
	if err != nil {
		logger.Error("Error getting folder parents", "error", err)
		if r.metrics != nil {
			r.metrics.RecordReconcileFailure(action, "permission_store")
		}
		return operator.ReconcileResult{}, err
	}

	if (len(parents) == 0 && parentUID == "") || (len(parents) == 1 && parents[0] == parentUID) {
		logger.Info("Folder is already reconciled", "folder", folderUID, "parent", parentUID, "namespace", namespace)
		if r.metrics != nil {
			r.metrics.RecordReconcileSuccess(action, "no_changes_needed")
		}
		return operator.ReconcileResult{}, nil
	}

	err = r.permissionStore.SetFolderParent(ctx, namespace, folderUID, parentUID)
	if err != nil {
		logger.Error("Error setting folder parent", "error", err)
		if r.metrics != nil {
			r.metrics.RecordReconcileFailure(action, "permission_store")
		}
		return operator.ReconcileResult{}, err
	}

	logger.Info("Folder parent set in permission store", "folder", folderUID, "parent", parentUID, "namespace", namespace)

	if r.metrics != nil {
		r.metrics.RecordReconcileSuccess(action, "changes_made")
	}

	return operator.ReconcileResult{}, nil
}

func (r *FolderReconciler) handleDeleteFolder(ctx context.Context, folder *foldersKind.Folder, action string) (operator.ReconcileResult, error) {
	logger := logging.FromContext(ctx)

	namespace := folder.Namespace
	folderUID := folder.Name

	err := r.permissionStore.DeleteFolderParents(ctx, namespace, folderUID)
	if err != nil {
		logger.Error("Error deleting folder parents", "error", err)
		if r.metrics != nil {
			r.metrics.RecordReconcileFailure(action, "permission_store")
		}
		return operator.ReconcileResult{}, err
	}

	logger.Info("Folder deleted from permission store", "folder", folderUID, "namespace", namespace)

	if r.metrics != nil {
		r.metrics.RecordReconcileSuccess(action, "changes_made")
	}

	return operator.ReconcileResult{}, nil
}

func validateFolder(folder *foldersKind.Folder) error {
	if folder == nil {
		return fmt.Errorf("folder is nil")
	}
	if folder.Name == "" {
		return fmt.Errorf("folder UID (ObjectMeta.Name) is empty")
	}
	if folder.Namespace == "" {
		return fmt.Errorf("folder namespace is empty")
	}
	return nil
}

func getFolderParent(ctx context.Context, folder *foldersKind.Folder) (string, error) {
	tracer := otel.GetTracerProvider().Tracer("iam-folder-reconciler")
	_, span := tracer.Start(ctx, "get-folder-parent",
		trace.WithAttributes(
			attribute.String("folder.uid", folder.Name),
		),
	)
	defer span.End()

	folderMeta, err := utils.MetaAccessor(folder)
	if err != nil {
		span.RecordError(err)
		span.SetStatus(codes.Error, "failed to get folder meta accessor")
		return "", err
	}
	return folderMeta.GetFolder(), nil
}
