// Copyright 2021 Google LLC
//
// 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 main

import (
	"context"
	"errors"
	"fmt"
	"io"
	"os"

	errors2 "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/ahmetb/kubectx/internal/kubeconfig"
	"github.com/ahmetb/kubectx/internal/printer"
)

type SwitchOp struct {
	Target string // '-' for back and forth, or NAME
	Force  bool   // force switch even if the namespace doesn't exist
}

func (s SwitchOp) Run(_, stderr io.Writer) error {
	kc := new(kubeconfig.Kubeconfig).WithLoader(kubeconfig.DefaultLoader)
	defer kc.Close()
	if err := kc.Parse(); err != nil {
		return fmt.Errorf("kubeconfig error: %w", err)
	}

	toNS, err := switchNamespace(kc, s.Target, s.Force)
	if err != nil {
		return err
	}
	err = printer.Success(stderr, "Active namespace is \"%s\"", printer.SuccessColor.Sprint(toNS))
	return err
}

func switchNamespace(kc *kubeconfig.Kubeconfig, ns string, force bool) (string, error) {
	ctx, err := kc.GetCurrentContext()
	if err != nil {
		return "", fmt.Errorf("failed to get current context: %w", err)
	}
	if ctx == "" {
		return "", errors.New("current-context is not set")
	}
	curNS, err := kc.NamespaceOfContext(ctx)
	if err != nil {
		return "", fmt.Errorf("failed to get current namespace: %w", err)
	}

	f := NewNSFile(ctx)
	prev, err := f.Load()
	if err != nil {
		return "", fmt.Errorf("failed to load previous namespace from file: %w", err)
	}

	if ns == "-" {
		if prev == "" {
			return "", fmt.Errorf("No previous namespace found for current context (%s)", ctx)
		}
		ns = prev
	}

	if !force {
		ok, err := namespaceExists(kc, ns)
		if err != nil {
			return "", fmt.Errorf("failed to query if namespace exists (is cluster accessible?): %w", err)
		}
		if !ok {
			return "", fmt.Errorf("no namespace exists with name \"%s\"", ns)
		}
	}

	if err := kc.SetNamespace(ctx, ns); err != nil {
		return "", fmt.Errorf("failed to change to namespace \"%s\": %w", ns, err)
	}
	if err := kc.Save(); err != nil {
		return "", fmt.Errorf("failed to save kubeconfig file: %w", err)
	}
	if curNS != ns {
		if err := f.Save(curNS); err != nil {
			return "", fmt.Errorf("failed to save the previous namespace to file: %w", err)
		}
	}
	return ns, nil
}

func namespaceExists(kc *kubeconfig.Kubeconfig, ns string) (bool, error) {
	// for tests
	if os.Getenv("_MOCK_NAMESPACES") != "" {
		return ns == "ns1" || ns == "ns2", nil
	}

	clientset, err := newKubernetesClientSet(kc)
	if err != nil {
		return false, fmt.Errorf("failed to initialize k8s REST client: %w", err)
	}

	namespace, err := clientset.CoreV1().Namespaces().Get(context.Background(), ns, metav1.GetOptions{})
	if errors2.IsNotFound(err) {
		return false, nil
	}
	if err != nil {
		return false, fmt.Errorf("failed to query namespace %q from k8s API: %w", ns, err)
	}
	return namespace != nil, nil
}
