package main

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/aisupport/apps/cli/ticket-manager/common"
	wsapi "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/ticket-manager/workspace"
	"github.com/tidbcloud/aisupport/packages/sdk-go/infra/auth/consts"
)

func TestSetupClientConfigure(t *testing.T) {
	clearAuthEnv(t)
	t.Setenv(consts.EnvDevOpsAPIToken, "test-token")

	err := common.SetupClient("http://ticket-manager.test", 3*time.Second)
	require.NoError(t, err)

	require.Equal(t, "http://ticket-manager.test/api/v1/ticketmanager", common.Client.BaseURL)
	require.Equal(t, 3*time.Second, common.Client.GetClient().Timeout)
	require.Equal(t, "application/json", common.Client.Header.Get("Accept"))
	require.Equal(t, "test-token", common.Client.Header.Get(consts.HeaderAPIToken))
}

func TestRootUsesTicketManagerURLEnv(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "/api/v1/ticketmanager/workspace/workspaces", r.URL.Path)
		require.Equal(t, "test-token", r.Header.Get(consts.HeaderAPIToken))
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"workspaces":[]}`))
	}))
	t.Cleanup(server.Close)
	t.Setenv("TICKET_MANAGER_URL", server.URL)
	clearAuthEnv(t)
	t.Setenv(consts.EnvDevOpsAPIToken, "test-token")

	root := NewRootCommand()
	var stdout bytes.Buffer
	root.SetOut(&stdout)
	root.SetArgs([]string{"workspace", "list"})

	err := root.Execute()
	require.NoError(t, err)
	require.Equal(t, server.URL+"/api/v1/ticketmanager", common.Client.BaseURL)
}

func TestRootRequiresDevOpsAPIToken(t *testing.T) {
	clearAuthEnv(t)

	root := NewRootCommand()
	var stdout bytes.Buffer
	root.SetOut(&stdout)
	root.SetArgs([]string{"workspace", "list"})

	err := root.Execute()
	require.Error(t, err)
	require.ErrorContains(t, err, consts.EnvDevOpsAPIToken)
	require.ErrorContains(t, err, consts.EnvDevOpsAccessToken)
	require.ErrorContains(t, err, consts.EnvDevOpsAPIKey)
	require.Contains(t, root.Long, consts.EnvDevOpsAPIToken)
	require.Contains(t, root.Long, consts.EnvDevOpsAccessToken)
	require.Contains(t, root.Long, consts.EnvDevOpsAPIKey)
	require.Empty(t, stdout.String())
}

func TestHTTPErrorReturnsRestyError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "/api/v1/ticketmanager/workspace/workspaces", r.URL.Path)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte(`{"errcode":500,"errmsg":"failed to load workspaces"}`))
	}))
	t.Cleanup(server.Close)
	clearAuthEnv(t)
	t.Setenv(consts.EnvDevOpsAPIToken, "test-token")

	root := NewRootCommand()
	var stdout bytes.Buffer
	var stderr bytes.Buffer
	root.SetOut(&stdout)
	root.SetErr(&stderr)
	root.SetArgs([]string{"--base-url", server.URL, "workspace", "list"})

	err := root.Execute()
	require.Error(t, err)
	require.ErrorContains(t, err, "list workspaces")
	require.ErrorContains(t, err, "status: 500 Internal Server Error")
	require.ErrorContains(t, err, `{"errcode":500,"errmsg":"failed to load workspaces"}`)
	require.Empty(t, stdout.String())
	require.Empty(t, stderr.String())
}

func clearAuthEnv(t *testing.T) {
	t.Helper()

	t.Setenv(consts.EnvDevOpsAccessToken, "")
	t.Setenv(consts.EnvDevOpsAPIToken, "")
	t.Setenv(consts.EnvDevOpsAPIKey, "")
}

func TestAdviceUsesFixedProxyRoute(t *testing.T) {
	for _, devopsEnv := range []string{"", "prod", "dev"} {
		t.Run("DEVOPS_ENV="+devopsEnv, func(t *testing.T) {
			var got wsapi.CreateAdviceRequest
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				require.Equal(t, http.MethodPost, r.Method)
				require.Equal(t, "/devops/prod/ticket-manager/api/v1/ticketmanager/workspace/advices", r.URL.Path)
				require.Empty(t, r.Header.Get(consts.HeaderAPIToken))
				require.Empty(t, r.Header.Get(consts.HeaderAPIKey))
				require.Empty(t, r.Header.Get("Authorization"))
				require.NoError(t, json.NewDecoder(r.Body).Decode(&got))
				w.Header().Set("Content-Type", "application/json")
				_, _ = w.Write([]byte(`{"advice":{"id":1}}`))
			}))
			t.Cleanup(server.Close)
			clearAuthEnv(t)
			t.Setenv(consts.EnvDevOpsAPIToken, "must-not-reach-sidecar")
			t.Setenv("DS_PROXY_URL", server.URL)
			t.Setenv("TICKET_MANAGER_URL", "http://unused.invalid")
			t.Setenv("DEVOPS_ENV", devopsEnv)
			dataFile := filepath.Join(t.TempDir(), "advice-data.json")
			require.NoError(t, os.WriteFile(dataFile, []byte(`{"add_task":{"title":"Scale cluster","description":"Follow up","priority":"p2","creator":"system"}}`), 0600))
			root := NewRootCommand()
			root.SetOut(&bytes.Buffer{})
			root.SetArgs([]string{"--base-url", server.URL + "/devops/prod/ticket-manager", "workspace", "advice", "create", "--workspace-id", "184", "--type", "add_task", "--description", "Scale cluster", "--data-file", dataFile, "--reply-message-id", "om_trigger"})
			require.NoError(t, root.Execute())
			require.EqualValues(t, 184, got.WorkspaceID)
			require.EqualValues(t, "add_task", got.Type)
			require.Equal(t, "om_trigger", got.ReplyMessageID)
			require.Equal(t, "Scale cluster", got.Data.AddTask.Title)
		})
	}
}

func TestSidecarExactBaseURL(t *testing.T) {
	for _, prefix := range []string{"", "/proxy", "/proxy/"} {
		t.Run("prefix="+prefix, func(t *testing.T) {
			requests := 0
			proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				requests++
				require.Equal(t, strings.TrimRight(prefix, "/")+"/api/v1/ticketmanager/workspace/workspaces", r.URL.Path)
				require.Empty(t, r.Header.Get(consts.HeaderAPIToken))
				require.Empty(t, r.Header.Get(consts.HeaderAPIKey))
				require.Empty(t, r.Header.Get("Authorization"))
				w.Header().Set("Content-Type", "application/json")
				_, _ = w.Write([]byte(`{"workspaces":[]}`))
			}))
			t.Cleanup(proxy.Close)
			clearAuthEnv(t)
			t.Setenv("DS_PROXY_URL", proxy.URL+prefix)
			for _, token := range []string{"", "must-not-reach-sidecar"} {
				t.Setenv(consts.EnvDevOpsAPIToken, token)
				root := NewRootCommand()
				root.SetOut(&bytes.Buffer{})
				root.SetArgs([]string{"--base-url", proxy.URL + prefix, "workspace", "list"})
				require.NoError(t, root.Execute())
			}
			require.Equal(t, 2, requests)
		})
	}
}

func TestSidecarDoesNotFollowRedirect(t *testing.T) {
	for _, route := range []string{"/devops/prod/ticket-manager", ""} {
		t.Run("route="+route, func(t *testing.T) {
			redirected := false
			upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				redirected = true
			}))
			t.Cleanup(upstream.Close)
			requested := false
			proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				requested = true
				http.Redirect(w, r, upstream.URL, http.StatusFound)
			}))
			t.Cleanup(proxy.Close)
			clearAuthEnv(t)
			t.Setenv("DS_PROXY_URL", proxy.URL+"/proxy")
			t.Setenv("TICKET_MANAGER_URL", proxy.URL+"/proxy"+route)
			root := NewRootCommand()
			root.SetOut(&bytes.Buffer{})
			root.SetArgs([]string{"workspace", "list"})
			require.Error(t, root.Execute())
			require.True(t, requested)
			require.False(t, redirected)
		})
	}
}
