package main

import (
	"bytes"
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
	"path"
	"testing"

	"github.com/influxdata/influx-cli/v2/pkg/cli/middleware"
	"github.com/stretchr/testify/require"
	"github.com/urfave/cli"
)

func TestApp_HostSpecificErrors(t *testing.T) {
	tests := []struct {
		name          string
		commandMw     cli.BeforeFunc
		svrBuild      string
		svrResCode    int
		wantErrString string
	}{
		{
			name:          "OSS command on Cloud host - with error",
			commandMw:     middleware.OSSOnly,
			svrBuild:      middleware.CloudBuildHeader,
			svrResCode:    http.StatusServiceUnavailable,
			wantErrString: fmt.Sprintf("%s\n", middleware.WrongHostErrString(middleware.OSSBuildHeader, middleware.CloudBuildHeader)),
		},
		{
			name:          "Cloud command on OSS host - with error",
			commandMw:     middleware.CloudOnly,
			svrBuild:      middleware.OSSBuildHeader,
			svrResCode:    http.StatusServiceUnavailable,
			wantErrString: fmt.Sprintf("%s\n", middleware.WrongHostErrString(middleware.CloudBuildHeader, middleware.OSSBuildHeader)),
		},
		{
			name:          "OSS command on OSS host - with error",
			commandMw:     middleware.OSSOnly,
			svrBuild:      middleware.OSSBuildHeader,
			svrResCode:    http.StatusServiceUnavailable,
			wantErrString: fmt.Sprintf("%s\n", "Error: health check failed: 503 Service Unavailable: unavailable"),
		},
		{
			name:          "Cloud command on Cloud host - with error",
			commandMw:     middleware.CloudOnly,
			svrBuild:      middleware.CloudBuildHeader,
			svrResCode:    http.StatusServiceUnavailable,
			wantErrString: fmt.Sprintf("%s\n", "Error: health check failed: 503 Service Unavailable: unavailable"),
		},
		{
			name:          "OSS command on Cloud host - no error",
			commandMw:     middleware.OSSOnly,
			svrBuild:      middleware.CloudBuildHeader,
			svrResCode:    http.StatusOK,
			wantErrString: "",
		},
		{
			name:          "Cloud command on OSS host - no error",
			commandMw:     middleware.CloudOnly,
			svrBuild:      middleware.OSSBuildHeader,
			svrResCode:    http.StatusOK,
			wantErrString: "",
		},
		{
			name:          "OSS command on OSS host - no error",
			commandMw:     middleware.OSSOnly,
			svrBuild:      middleware.OSSBuildHeader,
			svrResCode:    http.StatusOK,
			wantErrString: "",
		},
		{
			name:          "Cloud command on Cloud host - no error",
			commandMw:     middleware.CloudOnly,
			svrBuild:      middleware.CloudBuildHeader,
			svrResCode:    http.StatusOK,
			wantErrString: "",
		},
		{
			name:          "Non-specific command on OSS host - with error",
			commandMw:     nil,
			svrBuild:      middleware.OSSBuildHeader,
			svrResCode:    http.StatusServiceUnavailable,
			wantErrString: fmt.Sprintf("%s\n", "Error: health check failed: 503 Service Unavailable: unavailable"),
		},
		{
			name:          "Non-specific command on Cloud host - with error",
			commandMw:     nil,
			svrBuild:      middleware.CloudBuildHeader,
			svrResCode:    http.StatusServiceUnavailable,
			wantErrString: fmt.Sprintf("%s\n", "Error: health check failed: 503 Service Unavailable: unavailable"),
		},
		{
			name:          "Non-specific command on OSS host - no error",
			commandMw:     nil,
			svrBuild:      middleware.OSSBuildHeader,
			svrResCode:    http.StatusOK,
			wantErrString: "",
		},
		{
			name:          "Non-specific command on Cloud host - no error",
			commandMw:     nil,
			svrBuild:      middleware.CloudBuildHeader,
			svrResCode:    http.StatusOK,
			wantErrString: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cli.OsExiter = func(code int) {
				require.Equal(t, 1, code)
			}
			var b bytes.Buffer
			errWriter := &testWriter{
				b: &b,
			}
			cli.ErrWriter = errWriter

			svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.Header().Set("Content-Type", "application/json; charset=utf-8")
				w.Header().Set("X-Influxdb-Build", tt.svrBuild)
				w.WriteHeader(tt.svrResCode)
				_, err := fmt.Fprintf(w, `{"message":%q}`, "unavailable")
				require.NoError(t, err)
			}))
			defer svr.Close()
			cmd := newPingCmd()
			if tt.commandMw != nil {
				cmd = middleware.AddMWToCmds([]cli.Command{cmd}, tt.commandMw)[0]
			}
			app := newApp()
			app.Commands = []cli.Command{cmd}

			args := []string{
				"influx",
				"ping",
				"--configs-path",
				path.Join(os.TempDir(), "configs"),
				"--host",
				svr.URL,
			}

			_ = app.Run(args)
			require.Equal(t, tt.wantErrString, errWriter.b.String())
			if tt.wantErrString == "" {
				require.False(t, errWriter.written)
			}
		})
	}
}

func TestReplaceTokenArg(t *testing.T) {
	tests := []struct {
		name       string
		argsBefore []string
		argsAfter  []string
	}{
		{
			name: "short token flag second to last arg",
			argsBefore: []string{
				"config",
				"create",
				"-n",
				"targetflux",
				"-u",
				"http://localhost:8087",
				"-t",
				"-therestofmytoken",
				"-o",
				"organisation",
			},
			argsAfter: []string{
				"config",
				"create",
				"-n",
				"targetflux",
				"-u",
				"http://localhost:8087",
				"-t=-therestofmytoken",
				"-o",
				"organisation",
			},
		},
		{
			name: "long token flag second to last arg",
			argsBefore: []string{
				"config",
				"create",
				"-n",
				"targetflux",
				"-u",
				"http://localhost:8087",
				"--token",
				"-foo_token",
				"-o",
				"organisation",
			},
			argsAfter: []string{
				"config",
				"create",
				"-n",
				"targetflux",
				"-u",
				"http://localhost:8087",
				"--token=-foo_token",
				"-o",
				"organisation",
			},
		},
		{
			name: "short token flag last arg",
			argsBefore: []string{
				"config",
				"create",
				"-n",
				"targetflux",
				"-u",
				"http://localhost:8087",
				"-o",
				"organisation",
				"-t",
				"-therestofmytoken",
			},
			argsAfter: []string{
				"config",
				"create",
				"-n",
				"targetflux",
				"-u",
				"http://localhost:8087",
				"-o",
				"organisation",
				"-t=-therestofmytoken",
			},
		},
		{
			name: "long token flag last arg",
			argsBefore: []string{
				"config",
				"create",
				"-n",
				"targetflux",
				"-u",
				"http://localhost:8087",
				"-o",
				"organisation",
				"--token",
				"-foo_token",
			},
			argsAfter: []string{
				"config",
				"create",
				"-n",
				"targetflux",
				"-u",
				"http://localhost:8087",
				"-o",
				"organisation",
				"--token=-foo_token",
			},
		},
		{
			name: "no token flag",
			argsBefore: []string{
				"v1",
				"shell",
			},
			argsAfter: []string{
				"v1",
				"shell",
			},
		},
		{
			name: "unprovided token value for flag",
			argsBefore: []string{
				"v1",
				"shell",
				"--token",
			},
			argsAfter: []string{
				"v1",
				"shell",
				"--token",
			},
		},
		{
			name: "no token flag, other flags",
			argsBefore: []string{
				"v1",
				"shell",
				"-u",
				"http://localhost:8087",
				"-o",
				"organisation",
			},
			argsAfter: []string{
				"v1",
				"shell",
				"-u",
				"http://localhost:8087",
				"-o",
				"organisation",
			},
		},
		{
			name:       "no args",
			argsBefore: []string{},
			argsAfter:  []string{},
		},
	}
	for _, tt := range tests {
		newArgs := ReplaceTokenArg(tt.argsBefore)
		require.Equal(t, tt.argsAfter, newArgs)
	}
}

type testWriter struct {
	b       *bytes.Buffer
	written bool
}

func (w *testWriter) Write(p []byte) (int, error) {
	w.written = true
	return w.b.Write(p)
}
