// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s

package render

import (
	"fmt"
	"path"
	"strings"

	"github.com/derailed/k9s/internal/client"
	"github.com/derailed/k9s/internal/model1"
	"github.com/derailed/tcell/v2"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
)

const terminatingPhase = "Terminating"

// PersistentVolume renders a K8s PersistentVolume to screen.
type PersistentVolume struct {
	Base
}

// ColorerFunc colors a resource row.
func (PersistentVolume) ColorerFunc() model1.ColorerFunc {
	return func(ns string, h model1.Header, re *model1.RowEvent) tcell.Color {
		c := model1.DefaultColorer(ns, h, re)

		idx, ok := h.IndexOf("STATUS", true)
		if ok {
			return c
		}
		switch strings.TrimSpace(re.Row.Fields[idx]) {
		case string(v1.VolumeBound):
			return model1.StdColor
		case string(v1.VolumeAvailable):
			return tcell.ColorGreen
		case string(v1.VolumePending):
			return model1.PendingColor
		case terminatingPhase:
			return model1.CompletedColor
		}

		return c
	}
}

// Header returns a header row.
func (p PersistentVolume) Header(_ string) model1.Header {
	return p.doHeader(defaultPVHeader)
}

var defaultPVHeader = model1.Header{
	model1.HeaderColumn{Name: "NAME"},
	model1.HeaderColumn{Name: "CAPACITY", Attrs: model1.Attrs{Capacity: true}},
	model1.HeaderColumn{Name: "ACCESS MODES"},
	model1.HeaderColumn{Name: "RECLAIM POLICY"},
	model1.HeaderColumn{Name: "STATUS"},
	model1.HeaderColumn{Name: "CLAIM"},
	model1.HeaderColumn{Name: "STORAGECLASS"},
	model1.HeaderColumn{Name: "REASON"},
	model1.HeaderColumn{Name: "VOLUMEMODE", Attrs: model1.Attrs{Wide: true}},
	model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}},
	model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}},
	model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}},
}

// Render renders a K8s resource to screen.
func (p PersistentVolume) Render(o any, _ string, row *model1.Row) error {
	raw, ok := o.(*unstructured.Unstructured)
	if !ok {
		return fmt.Errorf("expected Unstructured, but got %T", o)
	}
	if err := p.defaultRow(raw, row); err != nil {
		return err
	}
	if p.specs.isEmpty() {
		return nil
	}
	cols, err := p.specs.realize(raw, defaultPVHeader, row)
	if err != nil {
		return err
	}
	cols.hydrateRow(row)

	return nil
}

func (p PersistentVolume) defaultRow(raw *unstructured.Unstructured, r *model1.Row) error {
	var pv v1.PersistentVolume
	err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &pv)
	if err != nil {
		return err
	}

	phase := pv.Status.Phase
	if pv.DeletionTimestamp != nil {
		phase = terminatingPhase
	}
	var claim string
	if pv.Spec.ClaimRef != nil {
		claim = path.Join(pv.Spec.ClaimRef.Namespace, pv.Spec.ClaimRef.Name)
	}
	class, found := pv.Annotations[v1.BetaStorageClassAnnotation]
	if !found {
		class = pv.Spec.StorageClassName
	}

	size := pv.Spec.Capacity[v1.ResourceStorage]

	r.ID = client.MetaFQN(&pv.ObjectMeta)
	r.Fields = model1.Fields{
		pv.Name,
		size.String(),
		accessMode(pv.Spec.AccessModes),
		string(pv.Spec.PersistentVolumeReclaimPolicy),
		string(phase),
		claim,
		class,
		pv.Status.Reason,
		p.volumeMode(pv.Spec.VolumeMode),
		mapToStr(pv.Labels),
		AsStatus(p.diagnose(phase)),
		ToAge(pv.GetCreationTimestamp()),
	}

	return nil
}

func (PersistentVolume) diagnose(phase v1.PersistentVolumePhase) error {
	if phase == v1.VolumeFailed {
		return fmt.Errorf("failed to delete or recycle")
	}

	return nil
}

func (PersistentVolume) volumeMode(m *v1.PersistentVolumeMode) string {
	if m == nil {
		return MissingValue
	}

	return string(*m)
}

// ----------------------------------------------------------------------------
// Helpers...

func accessMode(aa []v1.PersistentVolumeAccessMode) string {
	dd := accessDedup(aa)
	s := make([]string, 0, len(dd))
	for _, am := range dd {
		switch am {
		case v1.ReadWriteOnce:
			s = append(s, "RWO")
		case v1.ReadOnlyMany:
			s = append(s, "ROX")
		case v1.ReadWriteMany:
			s = append(s, "RWX")
		case v1.ReadWriteOncePod:
			s = append(s, "RWOP")
		}
	}

	return strings.Join(s, ",")
}

func accessContains(cc []v1.PersistentVolumeAccessMode, a v1.PersistentVolumeAccessMode) bool {
	for _, c := range cc {
		if c == a {
			return true
		}
	}

	return false
}

func accessDedup(cc []v1.PersistentVolumeAccessMode) []v1.PersistentVolumeAccessMode {
	set := []v1.PersistentVolumeAccessMode{}
	for _, c := range cc {
		if !accessContains(set, c) {
			set = append(set, c)
		}
	}

	return set
}
