package webhook

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/google/uuid"
	"github.com/goreleaser/goreleaser/internal/testctx"
	"github.com/goreleaser/goreleaser/internal/testlib"
	"github.com/goreleaser/goreleaser/pkg/config"
	"github.com/stretchr/testify/require"
)

func TestStringer(t *testing.T) {
	require.Equal(t, "webhook", Pipe{}.String())
}

func TestNoEndpoint(t *testing.T) {
	ctx := testctx.NewWithCfg(config.Project{
		Announce: config.Announce{
			Webhook: config.Webhook{},
		},
	})
	require.EqualError(t, Pipe{}.Announce(ctx), `webhook: no endpoint url`)
}

func TestMalformedEndpoint(t *testing.T) {
	ctx := testctx.NewWithCfg(config.Project{
		Announce: config.Announce{
			Webhook: config.Webhook{
				EndpointURL: "httxxx://example.com",
			},
		},
	})
	require.EqualError(t, Pipe{}.Announce(ctx), `webhook: Post "httxxx://example.com": unsupported protocol scheme "httxxx"`)
}

func TestAnnounceInvalidMessageTemplate(t *testing.T) {
	ctx := testctx.NewWithCfg(config.Project{
		Announce: config.Announce{
			Webhook: config.Webhook{
				EndpointURL:     "https://example.com/webhook",
				MessageTemplate: "{{ .Foo }",
			},
		},
	})
	testlib.RequireTemplateError(t, Pipe{}.Announce(ctx))
}

type WebHookServerMockMessage struct {
	Response string    `json:"response"`
	UUID     uuid.UUID `json:"uuid"`
}

func TestAnnounceWebhook(t *testing.T) {
	responseServer := WebHookServerMockMessage{
		Response: "Thanks for the announcement!",
		UUID:     uuid.New(),
	}

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer r.Body.Close()

		body, err := io.ReadAll(r.Body)
		require.NoError(t, err)
		require.Equal(t, "webhook-test", string(body))

		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		err = json.NewEncoder(w).Encode(responseServer)
		require.NoError(t, err)
	}))
	defer srv.Close()

	ctx := testctx.NewWithCfg(config.Project{
		ProjectName: "webhook-test",
		Announce: config.Announce{
			Webhook: config.Webhook{
				EndpointURL:     srv.URL,
				MessageTemplate: "{{ .ProjectName }}",
			},
		},
	})
	require.NoError(t, Pipe{}.Announce(ctx))
}

func TestAnnounceTLSWebhook(t *testing.T) {
	responseServer := WebHookServerMockMessage{
		Response: "Thanks for the announcement!",
		UUID:     uuid.New(),
	}

	srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer r.Body.Close()
		body, err := io.ReadAll(r.Body)
		require.NoError(t, err)
		require.Equal(t, "webhook-test", string(body))
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		err = json.NewEncoder(w).Encode(responseServer)
		require.NoError(t, err)
	}))
	defer srv.Close()
	fmt.Println(srv.URL)
	ctx := testctx.NewWithCfg(config.Project{
		ProjectName: "webhook-test",
		Announce: config.Announce{
			Webhook: config.Webhook{
				EndpointURL:     srv.URL,
				MessageTemplate: "{{ .ProjectName }}",
				SkipTLSVerify:   true,
			},
		},
	})
	require.NoError(t, Pipe{}.Announce(ctx))
}

func TestAnnounceTLSCheckCertWebhook(t *testing.T) {
	responseServer := WebHookServerMockMessage{
		Response: "Thanks for the announcement!",
		UUID:     uuid.New(),
	}

	srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer r.Body.Close()
		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		err := json.NewEncoder(w).Encode(responseServer)
		require.NoError(t, err)
	}))
	defer srv.Close()
	fmt.Println(srv.URL)
	ctx := testctx.NewWithCfg(config.Project{
		ProjectName: "webhook-test",
		Announce: config.Announce{
			Webhook: config.Webhook{
				EndpointURL:   srv.URL,
				SkipTLSVerify: false,
			},
		},
	})
	require.Error(t, Pipe{}.Announce(ctx))
}

func TestAnnounceBasicAuthWebhook(t *testing.T) {
	responseServer := WebHookServerMockMessage{
		Response: "Thanks for the announcement!",
		UUID:     uuid.New(),
	}

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer r.Body.Close()

		body, err := io.ReadAll(r.Body)
		require.NoError(t, err)
		require.Equal(t, "webhook-test", string(body))

		auth := r.Header.Get("Authorization")
		require.Equal(t, fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte("user:pass"))), auth)

		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		err = json.NewEncoder(w).Encode(responseServer)
		require.NoError(t, err)
	}))

	defer srv.Close()

	ctx := testctx.NewWithCfg(config.Project{
		ProjectName: "webhook-test",
		Announce: config.Announce{
			Webhook: config.Webhook{
				EndpointURL:     srv.URL,
				MessageTemplate: "{{ .ProjectName }}",
			},
		},
	})
	t.Setenv("BASIC_AUTH_HEADER_VALUE", fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte("user:pass"))))
	require.NoError(t, Pipe{}.Announce(ctx))
}

func TestAnnounceAdditionalHeadersWebhook(t *testing.T) {
	responseServer := WebHookServerMockMessage{
		Response: "Thanks for the announcement!",
		UUID:     uuid.New(),
	}

	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer r.Body.Close()

		body, err := io.ReadAll(r.Body)
		require.NoError(t, err)
		require.Equal(t, "webhook-test", string(body))

		customHeader := r.Header.Get("X-Custom-Header")
		require.Equal(t, "custom-value", customHeader)

		w.WriteHeader(http.StatusCreated)
		w.Header().Set("Content-Type", "application/json")
		err = json.NewEncoder(w).Encode(responseServer)
		require.NoError(t, err)
	}))
	defer srv.Close()

	ctx := testctx.NewWithCfg(config.Project{
		ProjectName: "webhook-test",
		Announce: config.Announce{
			Webhook: config.Webhook{
				EndpointURL:     srv.URL,
				MessageTemplate: "{{ .ProjectName }}",
				Headers: map[string]string{
					"X-Custom-Header": "custom-value",
				},
			},
		},
	})
	require.NoError(t, Pipe{}.Announce(ctx))
}

func TestSkip(t *testing.T) {
	t.Run("skip", func(t *testing.T) {
		require.True(t, Pipe{}.Skip(testctx.New()))
	})

	t.Run("dont skip", func(t *testing.T) {
		ctx := testctx.NewWithCfg(config.Project{
			Announce: config.Announce{
				Webhook: config.Webhook{
					Enabled: true,
				},
			},
		})
		require.False(t, Pipe{}.Skip(ctx))
	})
}