package cli

import (
	"context"
	"errors"
	"fmt"

	models "github.com/portainer/portainer/api/http/models/kubernetes"
	"github.com/rs/zerolog/log"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
)

// GetPersistentVolumeClaims returns all PVCs in the given namespace (or all namespaces if empty).
// For non-admin users, results are filtered to their accessible namespaces.
func (kcl *KubeClient) GetPersistentVolumeClaims(namespace string) ([]models.K8sPersistentVolumeClaim, error) {
	if kcl.GetIsKubeAdmin() {
		return kcl.fetchPersistentVolumeClaims(namespace)
	}

	return kcl.fetchPersistentVolumeClaimsForNonAdmin(namespace)
}

// GetPersistentVolumeClaim returns a single PVC by namespace and name.
func (kcl *KubeClient) GetPersistentVolumeClaim(namespace, name string) (*models.K8sPersistentVolumeClaim, error) {
	if !kcl.GetIsKubeAdmin() {
		allowedNamespaces := kcl.buildNonAdminNamespacesMap()
		if _, ok := allowedNamespaces[namespace]; !ok {
			return nil, fmt.Errorf("access to namespace %s is not authorized: %w", namespace, ErrUnauthorized)
		}
	}

	pvc, err := kcl.cli.CoreV1().PersistentVolumeClaims(namespace).Get(context.Background(), name, metav1.GetOptions{})
	if err != nil {
		return nil, fmt.Errorf("unable to get persistent volume claim %s/%s. Error: %w", namespace, name, err)
	}

	result := parsePersistentVolumeClaimDetail(pvc)
	return &result, nil
}

// DeletePersistentVolumeClaims deletes the specified PVCs.
func (kcl *KubeClient) DeletePersistentVolumeClaims(reqs models.K8sVolumeDeleteRequests) error {
	for _, req := range reqs {
		log.Debug().
			Str("context", "DeletePersistentVolumeClaims").
			Str("namespace", req.Namespace).
			Str("name", req.Name).
			Msg("Deleting persistent volume claim")

		err := kcl.cli.CoreV1().PersistentVolumeClaims(req.Namespace).Delete(context.Background(), req.Name, metav1.DeleteOptions{})
		if err != nil {
			return fmt.Errorf("unable to delete persistent volume claim %s/%s. Error: %w", req.Namespace, req.Name, err)
		}
	}

	return nil
}

// ResizePersistentVolumeClaim resizes a PVC to the given new size.
// The storage class must have AllowVolumeExpansion set to true.
func (kcl *KubeClient) ResizePersistentVolumeClaim(namespace, name, newSize string) error {
	pvc, err := kcl.cli.CoreV1().PersistentVolumeClaims(namespace).Get(context.Background(), name, metav1.GetOptions{})
	if err != nil {
		return fmt.Errorf("unable to get persistent volume claim %s/%s. Error: %w", namespace, name, err)
	}

	if pvc.Spec.StorageClassName != nil {
		sc, err := kcl.GetStorageClass(*pvc.Spec.StorageClassName)
		if err != nil {
			return fmt.Errorf("unable to get storage class %s. Error: %w", *pvc.Spec.StorageClassName, err)
		}

		if sc.AllowVolumeExpansion == nil || !*sc.AllowVolumeExpansion {
			return errors.New("storage class " + sc.Name + " does not allow volume expansion")
		}
	}

	_, err = resource.ParseQuantity(newSize)
	if err != nil {
		return fmt.Errorf("invalid size format %q. Error: %w", newSize, err)
	}

	patch := fmt.Sprintf(`{"spec":{"resources":{"requests":{"storage":"%s"}}}}`, newSize)

	_, err = kcl.cli.CoreV1().PersistentVolumeClaims(namespace).Patch(
		context.Background(),
		name,
		types.MergePatchType,
		[]byte(patch),
		metav1.PatchOptions{},
	)
	if err != nil {
		return fmt.Errorf("unable to resize persistent volume claim %s/%s. Error: %w", namespace, name, err)
	}

	return nil
}

func (kcl *KubeClient) fetchPersistentVolumeClaims(namespace string) ([]models.K8sPersistentVolumeClaim, error) {
	pvcList, err := kcl.cli.CoreV1().PersistentVolumeClaims(namespace).List(context.Background(), metav1.ListOptions{})
	if err != nil {
		return nil, fmt.Errorf("unable to list persistent volume claims. Error: %w", err)
	}

	storageClasses, err := kcl.GetStorageClasses()
	if err != nil {
		return nil, fmt.Errorf("unable to list storage classes for volume expansion lookup. Error: %w", err)
	}

	scExpansionMap := make(map[string]bool, len(storageClasses))
	for _, sc := range storageClasses {
		if sc.AllowVolumeExpansion != nil {
			scExpansionMap[sc.Name] = *sc.AllowVolumeExpansion
		}
	}

	results := make([]models.K8sPersistentVolumeClaim, 0, len(pvcList.Items))
	for i := range pvcList.Items {
		pvc := parsePersistentVolumeClaimDetail(&pvcList.Items[i])
		if pvc.StorageClass != nil {
			pvc.AllowVolumeExpansion = scExpansionMap[*pvc.StorageClass]
		}
		results = append(results, pvc)
	}

	return results, nil
}

func (kcl *KubeClient) fetchPersistentVolumeClaimsForNonAdmin(namespace string) ([]models.K8sPersistentVolumeClaim, error) {
	pvcs, err := kcl.fetchPersistentVolumeClaims(namespace)
	if err != nil {
		return nil, err
	}

	nonAdminNamespaceSet := kcl.buildNonAdminNamespacesMap()
	results := make([]models.K8sPersistentVolumeClaim, 0)
	for _, pvc := range pvcs {
		if _, ok := nonAdminNamespaceSet[pvc.Namespace]; ok {
			results = append(results, pvc)
		}
	}

	return results, nil
}

// parsePersistentVolumeClaimDetail parses a PVC into the model with access modes and storage request string.
func parsePersistentVolumeClaimDetail(pvc *corev1.PersistentVolumeClaim) models.K8sPersistentVolumeClaim {
	storage := pvc.Spec.Resources.Requests[corev1.ResourceStorage]
	return models.K8sPersistentVolumeClaim{
		ID:                       string(pvc.UID),
		Name:                     pvc.Name,
		Namespace:                pvc.Namespace,
		CreationDate:             pvc.CreationTimestamp.Time,
		Storage:                  storage.Value(),
		StorageRequest:           storage.String(),
		AccessModes:              humanReadableAccessModes(pvc.Spec.AccessModes),
		HumanReadableAccessModes: pvc.Spec.AccessModes,
		VolumeName:               pvc.Spec.VolumeName,
		ResourcesRequests:        &pvc.Spec.Resources.Requests,
		StorageClass:             pvc.Spec.StorageClassName,
		VolumeMode:               pvc.Spec.VolumeMode,
		OwningApplications:       nil,
		Phase:                    pvc.Status.Phase,
		Labels:                   pvc.Labels,
	}
}

// CombineClaimsWithApplications enriches each PVC with the workloads that mount it.
func (kcl *KubeClient) CombineClaimsWithApplications(pvcs []models.K8sPersistentVolumeClaim) ([]models.K8sPersistentVolumeClaim, error) {
	pods, err := kcl.cli.CoreV1().Pods("").List(context.Background(), metav1.ListOptions{})
	if err != nil {
		if k8serrors.IsNotFound(err) {
			return pvcs, nil
		}
		return nil, fmt.Errorf("failed to list pods: %w", err)
	}

	replicaSetItems := make([]appsv1.ReplicaSet, 0)
	deploymentItems := make([]appsv1.Deployment, 0)
	if containsReplicaSetOwnerReference(pods) {
		replicaSets, err := kcl.cli.AppsV1().ReplicaSets("").List(context.Background(), metav1.ListOptions{})
		if err != nil {
			return nil, fmt.Errorf("failed to list replica sets: %w", err)
		}
		replicaSetItems = replicaSets.Items

		deployments, err := kcl.cli.AppsV1().Deployments("").List(context.Background(), metav1.ListOptions{})
		if err != nil {
			return nil, fmt.Errorf("failed to list deployments: %w", err)
		}
		deploymentItems = deployments.Items
	}

	statefulSetItems := make([]appsv1.StatefulSet, 0)
	if containsStatefulSetOwnerReference(pods) {
		statefulSets, err := kcl.cli.AppsV1().StatefulSets("").List(context.Background(), metav1.ListOptions{})
		if err != nil {
			return nil, fmt.Errorf("failed to list stateful sets: %w", err)
		}
		statefulSetItems = statefulSets.Items
	}

	daemonSetItems := make([]appsv1.DaemonSet, 0)
	if containsDaemonSetOwnerReference(pods) {
		daemonSets, err := kcl.cli.AppsV1().DaemonSets("").List(context.Background(), metav1.ListOptions{})
		if err != nil {
			return nil, fmt.Errorf("failed to list daemon sets: %w", err)
		}
		daemonSetItems = daemonSets.Items
	}

	resources := PortainerApplicationResources{
		ReplicaSets:  replicaSetItems,
		Deployments:  deploymentItems,
		StatefulSets: statefulSetItems,
		DaemonSets:   daemonSetItems,
	}

	for i := range pvcs {
		for _, pod := range pods.Items {
			for _, podVolume := range pod.Spec.Volumes {
				if podVolume.PersistentVolumeClaim == nil {
					continue
				}
				if podVolume.PersistentVolumeClaim.ClaimName != pvcs[i].Name || pod.Namespace != pvcs[i].Namespace {
					continue
				}
				application, err := kcl.ConvertPodToApplication(pod, resources, false)
				if err != nil {
					return nil, fmt.Errorf("failed to convert pod to application: %w", err)
				}
				if application == nil {
					continue
				}
				alreadyAdded := false
				for _, existing := range pvcs[i].OwningApplications {
					if existing.Name == application.Name && existing.Namespace == application.Namespace {
						alreadyAdded = true
						break
					}
				}
				if !alreadyAdded {
					pvcs[i].OwningApplications = append(pvcs[i].OwningApplications, *application)
				}
			}
		}
	}

	return pvcs, nil
}
