package standard

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"testing"

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

	"github.com/nicholas-fedor/shoutrrr/internal/failures"
)

func TestStandard(t *testing.T) {
	gomega.RegisterFailHandler(ginkgo.Fail)
	ginkgo.RunSpecs(t, "Shoutrrr Standard Suite")
}

var (
	logger       *Logger
	builder      *strings.Builder
	stringLogger *log.Logger
)

var _ = ginkgo.Describe("the standard logging implementation", func() {
	ginkgo.When("setlogger is called with nil", func() {
		ginkgo.It("should provide the logging API without any errors", func() {
			logger = &Logger{}
			logger.SetLogger(nil)
			logger.Log("discarded log message")

			gomega.Expect(logger.logger).ToNot(gomega.BeNil())
		})
	})
	ginkgo.When("setlogger is called with a proper logger", func() {
		ginkgo.BeforeEach(func() {
			logger = &Logger{}
			builder = &strings.Builder{}
			stringLogger = log.New(builder, "", 0)
		})
		ginkgo.When("when  logger.Log is called", func() {
			ginkgo.It("should log messages", func() {
				logger.SetLogger(stringLogger)
				logger.Log("foo")
				logger.Log("bar")

				gomega.Expect(builder.String()).To(gomega.Equal("foo\nbar\n"))
			})
		})
		ginkgo.When("when  logger.Logf is called", func() {
			ginkgo.It("should log messages", func() {
				logger.SetLogger(stringLogger)
				logger.Logf("foo %d", 7)

				gomega.Expect(builder.String()).To(gomega.Equal("foo 7\n"))
			})
		})
	})
})

var _ = ginkgo.Describe("the standard template implementation", func() {
	ginkgo.When("a template is being set from a file", func() {
		ginkgo.It("should load the template without any errors", func() {
			file, err := os.CreateTemp("", "")
			if err != nil {
				ginkgo.Skip(fmt.Sprintf("Could not create temp file: %s", err))

				return
			}
			fileName := file.Name()
			defer os.Remove(fileName)

			_, err = io.WriteString(file, "template content")
			if err != nil {
				ginkgo.Skip(fmt.Sprintf("Could not write to temp file: %s", err))

				return
			}

			templater := &Templater{}
			err = templater.SetTemplateFile("foo", fileName)
			gomega.Expect(err).ToNot(gomega.HaveOccurred())
		})
	})
	ginkgo.When("a template is being set from a file that does not exist", func() {
		ginkgo.It("should return an error", func() {
			templater := &Templater{}
			err := templater.SetTemplateFile("foo", "filename_that_should_not_exist")
			gomega.Expect(err).To(gomega.HaveOccurred())
		})
	})
	ginkgo.When("a template is being set with a badly formatted string", func() {
		ginkgo.It("should return an error", func() {
			templater := &Templater{}
			err := templater.SetTemplateString("foo", "template {{ missing end tag")
			gomega.Expect(err).To(gomega.HaveOccurred())
		})
	})
	ginkgo.When("a template is being retrieved with a present ID", func() {
		ginkgo.It("should return the corresponding template", func() {
			templater := &Templater{}
			err := templater.SetTemplateString("bar", "template body")
			gomega.Expect(err).NotTo(gomega.HaveOccurred())

			tpl, found := templater.GetTemplate("bar")
			gomega.Expect(tpl).ToNot(gomega.BeNil())
			gomega.Expect(found).To(gomega.BeTrue())
		})
	})
	ginkgo.When("a template is being retrieved with an invalid ID", func() {
		ginkgo.It("should return an error", func() {
			templater := &Templater{}
			err := templater.SetTemplateString("bar", "template body")
			gomega.Expect(err).NotTo(gomega.HaveOccurred())

			tpl, found := templater.GetTemplate("bad ID")
			gomega.Expect(tpl).To(gomega.BeNil())
			gomega.Expect(found).ToNot(gomega.BeTrue())
		})
	})
})

var _ = ginkgo.Describe("the standard enumless config implementation", func() {
	ginkgo.When("it's enum method is called", func() {
		ginkgo.It("should return an empty map", func() {
			gomega.Expect((&EnumlessConfig{}).Enums()).To(gomega.BeEmpty())
		})
	})
})

var _ = ginkgo.Describe("the standard failure implementation", func() {
	ginkgo.Describe("Failure function", func() {
		ginkgo.When("called with FailParseURL", func() {
			ginkgo.It("should return a failure with the correct message", func() {
				err := errors.New("invalid URL")
				failure := Failure(FailParseURL, err)
				gomega.Expect(failure.ID()).To(gomega.Equal(FailParseURL))
				gomega.Expect(failure.Error()).
					To(gomega.ContainSubstring("error parsing Service URL"))
				gomega.Expect(failure.Error()).To(gomega.ContainSubstring("invalid URL"))
			})
		})
		ginkgo.When("called with FailUnknown", func() {
			ginkgo.It("should return a failure with the unknown error message", func() {
				err := errors.New("something went wrong")
				failure := Failure(FailUnknown, err)
				gomega.Expect(failure.ID()).To(gomega.Equal(FailUnknown))
				gomega.Expect(failure.Error()).
					To(gomega.ContainSubstring("an unknown error occurred"))
				gomega.Expect(failure.Error()).To(gomega.ContainSubstring("something went wrong"))
			})
		})
		ginkgo.When("called with an unrecognized FailureID", func() {
			ginkgo.It("should fallback to the unknown error message", func() {
				err := errors.New("unrecognized error")
				failure := Failure(failures.FailureID(999), err) // Arbitrary unknown ID
				gomega.Expect(failure.ID()).To(gomega.Equal(failures.FailureID(999)))
				gomega.Expect(failure.Error()).
					To(gomega.ContainSubstring("an unknown error occurred"))
				gomega.Expect(failure.Error()).To(gomega.ContainSubstring("unrecognized error"))
			})
		})
		ginkgo.When("called with additional arguments", func() {
			ginkgo.It("should include formatted arguments in the error", func() {
				err := errors.New("base error")
				failure := Failure(FailParseURL, err, "extra info: %s", "details")
				gomega.Expect(failure.Error()).
					To(gomega.ContainSubstring("error parsing Service URL extra info: details"))
				gomega.Expect(failure.Error()).To(gomega.ContainSubstring("base error"))
			})
		})
	})

	ginkgo.Describe("IsTestSetupFailure function", func() {
		ginkgo.When("called with a FailTestSetup failure", func() {
			ginkgo.It("should return true and the correct message", func() {
				err := errors.New("setup issue")
				failure := Failure(FailTestSetup, err)
				msg, isSetupFailure := IsTestSetupFailure(failure)
				gomega.Expect(isSetupFailure).To(gomega.BeTrue())
				gomega.Expect(msg).To(gomega.ContainSubstring("test setup failed: setup issue"))
			})
		})
		ginkgo.When("called with a different failure", func() {
			ginkgo.It("should return false and an empty message", func() {
				err := errors.New("parse issue")
				failure := Failure(FailParseURL, err)
				msg, isSetupFailure := IsTestSetupFailure(failure)
				gomega.Expect(isSetupFailure).To(gomega.BeFalse())
				gomega.Expect(msg).To(gomega.BeEmpty())
			})
		})
		ginkgo.When("called with nil", func() {
			ginkgo.It("should return false and an empty message", func() {
				msg, isSetupFailure := IsTestSetupFailure(nil)
				gomega.Expect(isSetupFailure).To(gomega.BeFalse())
				gomega.Expect(msg).To(gomega.BeEmpty())
			})
		})
	})
})
