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

package view

import (
	"context"
	"fmt"
	"log/slog"
	"strings"

	"github.com/derailed/k9s/internal/dao"
	"github.com/derailed/k9s/internal/slogs"
	"github.com/derailed/k9s/internal/ui"
	"github.com/derailed/tcell/v2"
	"github.com/derailed/tview"
	corev1 "k8s.io/api/core/v1"
)

const imageKey = "setImage"

type imageFormSpec struct {
	name, dockerImage, newDockerImage string
	init                              bool
}

func (m *imageFormSpec) modified() bool {
	newDockerImage := strings.TrimSpace(m.newDockerImage)
	return newDockerImage != "" && m.dockerImage != newDockerImage
}

func (m *imageFormSpec) imageSpec() dao.ImageSpec {
	ret := dao.ImageSpec{
		Name: m.name,
		Init: m.init,
	}

	if m.modified() {
		ret.DockerImage = strings.TrimSpace(m.newDockerImage)
	} else {
		ret.DockerImage = m.dockerImage
	}

	return ret
}

// ImageExtender provides for overriding container images.
type ImageExtender struct {
	ResourceViewer
}

// NewImageExtender returns a new extender.
func NewImageExtender(r ResourceViewer) ResourceViewer {
	s := ImageExtender{ResourceViewer: r}
	s.AddBindKeysFn(s.bindKeys)

	return &s
}

func (s *ImageExtender) bindKeys(aa *ui.KeyActions) {
	if s.App().Config.IsReadOnly() {
		return
	}
	aa.Add(ui.KeyI, ui.NewKeyAction("Set Image", s.setImageCmd, false))
}

func (s *ImageExtender) setImageCmd(*tcell.EventKey) *tcell.EventKey {
	path := s.GetTable().GetSelectedItem()
	if path == "" {
		return nil
	}

	s.Stop()
	defer s.Start()
	if err := s.showImageDialog(path); err != nil {
		s.App().Flash().Err(err)
	}

	return nil
}

func (s *ImageExtender) showImageDialog(path string) error {
	form, err := s.makeSetImageForm(path)
	if err != nil {
		return err
	}
	confirm := tview.NewModalForm("<Set image>", form)
	confirm.SetText(fmt.Sprintf("Set image %s %s", s.GVR(), path))
	confirm.SetDoneFunc(func(int, string) {
		s.dismissDialog()
	})
	s.App().Content.AddPage(imageKey, confirm, false, false)
	s.App().Content.ShowPage(imageKey)

	return nil
}

func (s *ImageExtender) makeSetImageForm(fqn string) (*tview.Form, error) {
	podSpec, err := s.getPodSpec(fqn)
	if err != nil {
		return nil, err
	}

	formContainerLines := make([]*imageFormSpec, 0, len(podSpec.InitContainers)+len(podSpec.Containers))
	for i := range podSpec.InitContainers {
		spec := podSpec.InitContainers[i]
		formContainerLines = append(formContainerLines, &imageFormSpec{init: true, name: spec.Name, dockerImage: spec.Image})
	}
	for i := range podSpec.Containers {
		spec := podSpec.Containers[i]
		formContainerLines = append(formContainerLines, &imageFormSpec{name: spec.Name, dockerImage: spec.Image})
	}

	styles := s.App().Styles.Dialog()
	f := tview.NewForm().
		SetItemPadding(0).
		SetButtonsAlign(tview.AlignCenter).
		SetButtonBackgroundColor(styles.ButtonBgColor.Color()).
		SetButtonTextColor(styles.ButtonFgColor.Color()).
		SetLabelColor(styles.LabelFgColor.Color()).
		SetFieldTextColor(styles.FieldFgColor.Color()).
		AddButton("OK", func() {
			defer s.dismissDialog()
			var imageSpecsModified dao.ImageSpecs
			for _, v := range formContainerLines {
				if v.modified() {
					imageSpecsModified = append(imageSpecsModified, v.imageSpec())
				}
			}
			ctx, cancel := context.WithTimeout(context.Background(), s.App().Conn().Config().CallTimeout())
			defer cancel()
			if err := s.setImages(ctx, fqn, imageSpecsModified); err != nil {
				slog.Error("Unable to set image name",
					slogs.FQN, fqn,
					slogs.Error, err,
				)
				s.App().Flash().Err(err)
				return
			}
			s.App().Flash().Infof("Resource %s:%s image updated successfully", s.GVR(), fqn)
		}).
		AddButton("Cancel", func() {
			s.dismissDialog()
		})

	for i := range formContainerLines {
		ctn := formContainerLines[i]
		f.AddInputField(ctn.name, ctn.dockerImage, 0, nil, func(changed string) {
			ctn.newDockerImage = changed
		})
	}

	for i := range f.GetButtonCount() {
		f.GetButton(i).
			SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color()).
			SetLabelColorActivated(styles.ButtonFocusFgColor.Color())
	}

	return f, nil
}

func (s *ImageExtender) dismissDialog() {
	s.App().Content.RemovePage(imageKey)
}

func (s *ImageExtender) getPodSpec(path string) (*corev1.PodSpec, error) {
	res, err := dao.AccessorFor(s.App().factory, s.GVR())
	if err != nil {
		return nil, err
	}
	resourceWPodSpec, ok := res.(dao.ContainsPodSpec)
	if !ok {
		return nil, fmt.Errorf("expecting a ContainsPodSpec for %q but got %T", s.GVR(), res)
	}

	return resourceWPodSpec.GetPodSpec(path)
}

func (s *ImageExtender) setImages(ctx context.Context, path string, imageSpecs dao.ImageSpecs) error {
	res, err := dao.AccessorFor(s.App().factory, s.GVR())
	if err != nil {
		return err
	}

	resourceWPodSpec, ok := res.(dao.ContainsPodSpec)
	if !ok {
		return fmt.Errorf("expecting a scalable resource for %q", s.GVR())
	}

	return resourceWPodSpec.SetImages(ctx, path, imageSpecs)
}
