package cli

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"time"

	"github.com/pkg/errors"
	"github.com/replicatedhq/troubleshoot/internal/util"
	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
	"github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
	"github.com/replicatedhq/troubleshoot/pkg/collect"
	"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
	"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
	"github.com/replicatedhq/troubleshoot/pkg/specs"
	"github.com/replicatedhq/troubleshoot/pkg/supportbundle"
	"github.com/spf13/viper"
	"k8s.io/apimachinery/pkg/labels"
)

const (
	defaultTimeout = 30 * time.Second
)

func runCollect(v *viper.Viper, arg string) error {
	go func() {
		signalChan := make(chan os.Signal, 1)
		signal.Notify(signalChan, os.Interrupt)
		<-signalChan
		os.Exit(0)
	}()

	var collectorContent []byte
	var err error
	if strings.HasPrefix(arg, "secret/") {
		// format secret/namespace-name/secret-name
		pathParts := strings.Split(arg, "/")
		if len(pathParts) != 3 {
			return errors.Errorf("path %s must have 3 components", arg)
		}

		spec, err := specs.LoadFromSecret(pathParts[1], pathParts[2], "collect-spec")
		if err != nil {
			return errors.Wrap(err, "failed to get spec from secret")
		}

		collectorContent = spec
	} else if arg == "-" {
		b, err := io.ReadAll(os.Stdin)
		if err != nil {
			return err
		}

		collectorContent = b
	} else if _, err = os.Stat(arg); err == nil {
		b, err := os.ReadFile(arg)
		if err != nil {
			return err
		}

		collectorContent = b
	} else {
		if !util.IsURL(arg) {
			return fmt.Errorf("%s is not a URL and was not found", arg)
		}

		req, err := http.NewRequest("GET", arg, nil)
		if err != nil {
			return err
		}
		req.Header.Set("User-Agent", "Replicated_Collect/v1beta2")
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			return err
		}
		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}

		collectorContent = body
	}

	collectorContent, err = docrewrite.ConvertToV1Beta2(collectorContent)
	if err != nil {
		return errors.Wrap(err, "failed to convert to v1beta2")
	}

	multidocs := strings.Split(string(collectorContent), "\n---\n")

	decode := scheme.Codecs.UniversalDeserializer().Decode

	redactors, err := supportbundle.GetRedactorsFromURIs(v.GetStringSlice("redactors"))
	if err != nil {
		return errors.Wrap(err, "failed to get redactors")
	}

	additionalRedactors := &troubleshootv1beta2.Redactor{
		Spec: troubleshootv1beta2.RedactorSpec{
			Redactors: redactors,
		},
	}

	for i, additionalDoc := range multidocs {
		if i == 0 {
			continue
		}
		additionalDoc, err := docrewrite.ConvertToV1Beta2([]byte(additionalDoc))
		if err != nil {
			return errors.Wrap(err, "failed to convert to v1beta2")
		}
		obj, _, err := decode(additionalDoc, nil, nil)
		if err != nil {
			return errors.Wrapf(err, "failed to parse additional doc %d", i)
		}
		multidocRedactors, ok := obj.(*troubleshootv1beta2.Redactor)
		if !ok {
			continue
		}
		additionalRedactors.Spec.Redactors = append(additionalRedactors.Spec.Redactors, multidocRedactors.Spec.Redactors...)
	}

	// make sure we don't block any senders
	progressCh := make(chan interface{})
	defer close(progressCh)
	go func() {
		for range progressCh {
		}
	}()

	restConfig, err := k8sutil.GetRESTConfig()
	if err != nil {
		return errors.Wrap(err, "failed to convert kube flags to rest config")
	}

	labelSelector, err := labels.Parse(v.GetString("selector"))
	if err != nil {
		return errors.Wrap(err, "unable to parse selector")
	}

	namespace := v.GetString("namespace")
	if namespace == "" {
		namespace = "default"
	}

	timeout := v.GetDuration("request-timeout")
	if timeout == 0 {
		timeout = defaultTimeout
	}

	createOpts := collect.CollectorRunOpts{
		CollectWithoutPermissions: v.GetBool("collect-without-permissions"),
		KubernetesRestConfig:      restConfig,
		Image:                     v.GetString("collector-image"),
		PullPolicy:                v.GetString("collector-pullpolicy"),
		LabelSelector:             labelSelector.String(),
		Namespace:                 namespace,
		Timeout:                   timeout,
		ProgressChan:              progressCh,
	}

	// we only support HostCollector or RemoteCollector kinds.
	hostCollector, err := collect.ParseHostCollectorFromDoc([]byte(multidocs[0]))
	if err == nil {
		results, err := collect.CollectHost(hostCollector, additionalRedactors, createOpts)
		if err != nil {
			return errors.Wrap(err, "failed to collect from host")
		}
		return showHostStdoutResults(v.GetString("format"), hostCollector.Name, results)
	}

	remoteCollector, err := collect.ParseRemoteCollectorFromDoc([]byte(multidocs[0]))
	if err == nil {
		results, err := collect.CollectRemote(remoteCollector, additionalRedactors, createOpts)
		if err != nil {
			return errors.Wrap(err, "failed to collect from remote host(s)")
		}
		return showRemoteStdoutResults(v.GetString("format"), remoteCollector.Name, results)
	}

	return errors.New("failed to parse hostCollector or remoteCollector")
}
