package karmadactl

import (
	"context"
	"fmt"
	"os"

	"github.com/spf13/cobra"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/client-go/rest"
	kubectllogs "k8s.io/kubectl/pkg/cmd/logs"
	cmdutil "k8s.io/kubectl/pkg/cmd/util"
	"k8s.io/kubectl/pkg/util/templates"

	karmadaclientset "github.com/karmada-io/karmada/pkg/generated/clientset/versioned"
	"github.com/karmada-io/karmada/pkg/karmadactl/options"
)

const (
	logsUsageStr = "logs [-f] [-p] (POD | TYPE/NAME) [-c CONTAINER] (-C CLUSTER)"
)

var (
	logsUsageErrStr = fmt.Sprintf("expected '%s'.\nPOD or TYPE/NAME is a required argument for the logs command", logsUsageStr)
	logsExample     = templates.Examples(`
		# Return snapshot logs from pod nginx with only one container in cluster(member1)
		%[1]s logs nginx -C=member1
	
		# Return snapshot logs from pod nginx with multi containers in cluster(member1)
		%[1]s logs nginx --all-containers=true -C=member1
	
		# Return snapshot logs from all containers in pods defined by label app=nginx in cluster(member1)
		%[1]s logs -l app=nginx --all-containers=true -C=member1
	
		# Return snapshot of previous terminated ruby container logs from pod web-1 in cluster(member1)
		%[1]s logs -p -c ruby web-1 -C=member1
	
		# Begin streaming the logs of the ruby container in pod web-1 in cluster(member1)
		%[1]s logs -f -c ruby web-1 -C=member1
	
		# Begin streaming the logs from all containers in pods defined by label app=nginx in cluster(member1)
		%[1]s logs -f -l app=nginx --all-containers=true -C=member1
	
		# Display only the most recent 20 lines of output in pod nginx in cluster(member1)
		%[1]s logs --tail=20 nginx -C=member1
	
		# Show all logs from pod nginx written in the last hour in cluster(member1)
		%[1]s logs --since=1h nginx -C=member1`)
)

// NewCmdLogs new logs command.
func NewCmdLogs(karmadaConfig KarmadaConfig, parentCommand string, streams genericclioptions.IOStreams) *cobra.Command {
	o := &LogsOptions{
		KubectlLogsOptions: kubectllogs.NewLogsOptions(streams, false),
	}

	cmd := &cobra.Command{
		Use:          logsUsageStr,
		Short:        "Print the logs for a container in a pod in a cluster",
		SilenceUsage: true,
		Example:      fmt.Sprintf(logsExample, parentCommand),
		RunE: func(cmd *cobra.Command, args []string) error {
			if err := o.Complete(karmadaConfig, cmd, args); err != nil {
				return err
			}
			if err := o.Validate(); err != nil {
				return err
			}
			if err := o.Run(); err != nil {
				return err
			}
			return nil
		},
	}

	o.GlobalCommandOptions.AddFlags(cmd.Flags())
	o.KubectlLogsOptions.AddFlags(cmd)
	cmd.Flags().StringVarP(&o.Namespace, "namespace", "n", o.Namespace, "If present, the namespace scope for this CLI request")
	cmd.Flags().StringVarP(&o.Cluster, "cluster", "C", "", "Specify a member cluster")
	return cmd
}

// LogsOptions contains the input to the logs command.
type LogsOptions struct {
	// global flags
	options.GlobalCommandOptions
	// flags specific to logs
	KubectlLogsOptions *kubectllogs.LogsOptions
	Namespace          string
	Cluster            string
}

// Complete ensures that options are valid and marshals them if necessary
func (o *LogsOptions) Complete(karmadaConfig KarmadaConfig, cmd *cobra.Command, args []string) error {
	if o.Cluster == "" {
		return fmt.Errorf("must specify a cluster")
	}

	// print correct usage message when the given arguments are invalid
	switch len(args) {
	case 0:
		if len(o.KubectlLogsOptions.Selector) == 0 {
			return cmdutil.UsageErrorf(cmd, "%s", logsUsageErrStr)
		}
	case 1:
		if len(o.KubectlLogsOptions.Selector) != 0 {
			return cmdutil.UsageErrorf(cmd, "only a selector (-l) or a POD name is allowed")
		}
	case 2:
	default:
		return cmdutil.UsageErrorf(cmd, "%s", logsUsageErrStr)
	}

	karmadaRestConfig, err := karmadaConfig.GetRestConfig(o.KarmadaContext, o.KubeConfig)
	if err != nil {
		return fmt.Errorf("failed to get control plane rest config. context: %s, kube-config: %s, error: %v",
			o.KarmadaContext, o.KubeConfig, err)
	}
	clusterInfo, err := getClusterInfo(karmadaRestConfig, o.Cluster, o.KubeConfig, o.KarmadaContext)
	if err != nil {
		return err
	}
	f := getFactory(o.Cluster, clusterInfo, o.Namespace)
	return o.KubectlLogsOptions.Complete(f, cmd, args)
}

// Validate checks to the LogsOptions to see if there is sufficient information run the command
func (o *LogsOptions) Validate() error {
	return o.KubectlLogsOptions.Validate()
}

// Run retrieves a pod log
func (o *LogsOptions) Run() error {
	return o.KubectlLogsOptions.RunLogs()
}

// getClusterInfo get information of cluster
func getClusterInfo(karmadaRestConfig *rest.Config, clusterName, kubeConfig, karmadaContext string) (map[string]*ClusterInfo, error) {
	clusterClient := karmadaclientset.NewForConfigOrDie(karmadaRestConfig).ClusterV1alpha1().Clusters()

	// check if the cluster exist in karmada control plane
	_, err := clusterClient.Get(context.TODO(), clusterName, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}

	clusterInfos := make(map[string]*ClusterInfo)
	clusterInfos[clusterName] = &ClusterInfo{}

	clusterInfos[clusterName].APIEndpoint = karmadaRestConfig.Host + fmt.Sprintf(proxyURL, clusterName)
	clusterInfos[clusterName].KubeConfig = kubeConfig
	clusterInfos[clusterName].Context = karmadaContext
	if clusterInfos[clusterName].KubeConfig == "" {
		env := os.Getenv("KUBECONFIG")
		if env != "" {
			clusterInfos[clusterName].KubeConfig = env
		} else {
			clusterInfos[clusterName].KubeConfig = defaultKubeConfig
		}
	}

	return clusterInfos, nil
}
