package wecom

import (
	"errors"
	"log"
	"net/http"
	"strings"
	"testing"

	"github.com/jarcoal/httpmock"
	"github.com/onsi/ginkgo/v2"
	"github.com/onsi/gomega"

	"github.com/nicholas-fedor/shoutrrr/internal/testutils"
	"github.com/nicholas-fedor/shoutrrr/pkg/format"
	"github.com/nicholas-fedor/shoutrrr/pkg/types"
)

const fullURL = "wecom://693axxx6-7aoc-4bc4-97a0-0ec2sifa5aaa"

var (
	service *Service
	logger  *log.Logger
	_       = ginkgo.BeforeSuite(func() {
		logger = log.New(ginkgo.GinkgoWriter, "Test", log.LstdFlags)
	})
)

var _ = ginkgo.Describe("WeCom Test", func() {
	ginkgo.BeforeEach(func() {
		service = &Service{}
	})

	ginkgo.When("parsing the configuration URL", func() {
		ginkgo.It("should be identical after de-/serialization", func() {
			url := testutils.URLMust(fullURL)
			config := &Config{}
			pkr := format.NewPropKeyResolver(config)
			err := config.setURL(&pkr, url)
			gomega.Expect(err).ShouldNot(gomega.HaveOccurred())

			outputURL := config.GetURL()
			ginkgo.GinkgoT().Logf("\n\n%s\n%s\n\n-", outputURL, fullURL)
			gomega.Expect(outputURL.String()).To(gomega.Equal(fullURL))
		})
	})

	ginkgo.Context("basic service API methods", func() {
		var config *Config

		ginkgo.BeforeEach(func() {
			config = &Config{}
		})
		ginkgo.It("should not allow getting invalid query values", func() {
			testutils.TestConfigGetInvalidQueryValue(config)
		})
		ginkgo.It("should not allow setting invalid query values", func() {
			testutils.TestConfigSetInvalidQueryValue(
				config,
				"wecom://693axxx6-7aoc-4bc4-97a0-0ec2sifa5aaa?foo=bar",
			)
		})
		ginkgo.It("should have the expected number of fields and enums", func() {
			testutils.TestConfigGetEnumsCount(config, 0)
			testutils.TestConfigGetFieldsCount(config, 3)
		})
	})

	ginkgo.When("initializing the service", func() {
		ginkgo.It("should fail with invalid key format", func() {
			err := service.Initialize(testutils.URLMust("wecom://invalid.key"), logger)
			gomega.Expect(err).
				To(gomega.MatchError(gomega.ContainSubstring("invalid WeCom webhook key format")))
		})
		ginkgo.It("should fail with empty key", func() {
			err := service.Initialize(testutils.URLMust("wecom://"), logger)
			gomega.Expect(err).
				To(gomega.MatchError(gomega.ContainSubstring("WeCom webhook key cannot be empty")))
		})
	})

	ginkgo.When("sending a message", func() {
		ginkgo.When("the message is too large", func() {
			ginkgo.It("should return large message error", func() {
				data := make([]string, 410)
				for i := range data {
					data[i] = "0123456789"
				}

				message := strings.Join(data, "")
				service := Service{Config: &Config{Key: "693axxx6-7aoc-4bc4-97a0-0ec2sifa5aaa"}}
				gomega.Expect(service.Send(message, nil)).To(gomega.MatchError(ErrLargeMessage))
			})
		})

		ginkgo.When("an invalid param is passed", func() {
			ginkgo.It("should fail to send messages", func() {
				service := Service{Config: &Config{Key: "693axxx6-7aoc-4bc4-97a0-0ec2sifa5aaa"}}
				gomega.Expect(
					service.Send("test message", &types.Params{"invalid": "value"}),
				).To(gomega.MatchError(gomega.ContainSubstring("not a valid config key: invalid")))
			})
		})

		ginkgo.Context("sending message by HTTP", func() {
			ginkgo.BeforeEach(func() {
				httpmock.ActivateNonDefault(httpClient)
			})
			ginkgo.AfterEach(func() {
				httpmock.DeactivateAndReset()
			})

			ginkgo.It("should send text message successfully", func() {
				httpmock.RegisterResponder(
					http.MethodPost,
					"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=693axxx6-7aoc-4bc4-97a0-0ec2sifa5aaa",
					httpmock.NewJsonResponderOrPanic(
						http.StatusOK,
						map[string]any{"errcode": 0, "errmsg": "ok"},
					),
				)

				err := service.Initialize(testutils.URLMust(fullURL), logger)
				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
				err = service.Send("message", nil)
				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
			})

			ginkgo.It("should send message with mentions successfully", func() {
				httpmock.RegisterResponder(
					http.MethodPost,
					"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=693axxx6-7aoc-4bc4-97a0-0ec2sifa5aaa",
					httpmock.NewJsonResponderOrPanic(
						http.StatusOK,
						map[string]any{"errcode": 0, "errmsg": "ok"},
					),
				)

				err := service.Initialize(testutils.URLMust(fullURL), logger)
				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
				err = service.Send("message", &types.Params{"mentioned_list": "@all"})
				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
			})

			ginkgo.It("should return error on network failure", func() {
				httpmock.RegisterResponder(
					http.MethodPost,
					"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=693axxx6-7aoc-4bc4-97a0-0ec2sifa5aaa",
					httpmock.NewErrorResponder(errors.New("network error")),
				)

				err := service.Initialize(testutils.URLMust(fullURL), logger)
				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
				err = service.Send("message", nil)
				gomega.Expect(err).To(gomega.MatchError(gomega.ContainSubstring("network error")))
			})

			ginkgo.It("should return error on invalid JSON response", func() {
				httpmock.RegisterResponder(
					http.MethodPost,
					"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=693axxx6-7aoc-4bc4-97a0-0ec2sifa5aaa",
					httpmock.NewStringResponder(http.StatusOK, "some response"),
				)

				err := service.Initialize(testutils.URLMust(fullURL), logger)
				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
				err = service.Send("message", nil)
				gomega.Expect(err).
					To(gomega.MatchError(gomega.ContainSubstring("invalid character")))
			})

			ginkgo.It("should return error on non-zero response code", func() {
				httpmock.RegisterResponder(
					http.MethodPost,
					"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=693axxx6-7aoc-4bc4-97a0-0ec2sifa5aaa",
					httpmock.NewJsonResponderOrPanic(
						http.StatusOK,
						map[string]any{"errcode": 1, "errmsg": "some error"},
					),
				)

				err := service.Initialize(testutils.URLMust(fullURL), logger)
				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
				err = service.Send("message", nil)
				gomega.Expect(err).To(gomega.MatchError(gomega.ContainSubstring("some error")))
			})

			ginkgo.It("should fail on HTTP 400 status", func() {
				httpmock.RegisterResponder(
					http.MethodPost,
					"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=693axxx6-7aoc-4bc4-97a0-0ec2sifa5aaa",
					httpmock.NewStringResponder(http.StatusBadRequest, "bad request"),
				)

				err := service.Initialize(testutils.URLMust(fullURL), logger)
				gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
				err = service.Send("message", nil)
				gomega.Expect(err).
					To(gomega.MatchError(gomega.ContainSubstring("unexpected status 400")))
			})
		})
	})
})

func TestWeCom(t *testing.T) {
	t.Parallel()
	gomega.RegisterFailHandler(ginkgo.Fail)
	ginkgo.RunSpecs(t, "Shoutrrr WeCom Suite")
}
