package failures_test

import (
	"errors"
	"fmt"
	"testing"

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

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

// TestFailures runs the Ginkgo test suite for the failures package.
func TestFailures(t *testing.T) {
	format.CharactersAroundMismatchToInclude = 20 // Show more context in failure output

	gomega.RegisterFailHandler(ginkgo.Fail)
	ginkgo.RunSpecs(t, "Failure Suite")
}

var _ = ginkgo.Describe("the failure package", func() {
	// Common test fixtures
	var (
		testID      failures.FailureID = 42                             // Consistent ID for testing
		testMessage                    = "test failure occurred"        // Sample error message
		wrappedErr                     = errors.New("underlying error") // Sample wrapped error
	)

	ginkgo.Describe("Wrap function", func() {
		ginkgo.When("creating a basic failure", func() {
			ginkgo.It("returns a failure with the provided message and ID", func() {
				failure := failures.Wrap(testMessage, testID, nil)
				gomega.Expect(failure.Error()).To(gomega.Equal(testMessage))
				gomega.Expect(failure.ID()).To(gomega.Equal(testID))
				gomega.Expect(failure.Unwrap()).To(gomega.Succeed())
			})
		})

		ginkgo.When("wrapping an existing error", func() {
			ginkgo.It("combines the message and wrapped error", func() {
				failure := failures.Wrap(testMessage, testID, wrappedErr)
				expectedError := fmt.Sprintf("%s: %v", testMessage, wrappedErr)
				gomega.Expect(failure.Error()).To(gomega.Equal(expectedError))
				gomega.Expect(failure.ID()).To(gomega.Equal(testID))
				gomega.Expect(failure.Unwrap()).To(gomega.Equal(wrappedErr))
			})
		})

		ginkgo.When("using formatted message with arguments", func() {
			ginkgo.It("formats the message correctly", func() {
				formatMessage := "test failure %d"
				failure := failures.Wrap(formatMessage, testID, nil, 123)
				gomega.Expect(failure.Error()).To(gomega.Equal("test failure 123"))
				gomega.Expect(failure.ID()).To(gomega.Equal(testID))
			})
		})
	})

	ginkgo.Describe("Failure interface methods", func() {
		var failure failures.Failure

		// Setup a failure with a wrapped error before each test
		ginkgo.BeforeEach(func() {
			failure = failures.Wrap(testMessage, testID, wrappedErr)
		})

		ginkgo.Describe("Error method", func() {
			ginkgo.It("returns only the message when no wrapped error exists", func() {
				failureNoWrap := failures.Wrap(testMessage, testID, nil)
				gomega.Expect(failureNoWrap.Error()).To(gomega.Equal(testMessage))
			})
			ginkgo.It("combines message with wrapped error", func() {
				expected := fmt.Sprintf("%s: %v", testMessage, wrappedErr)
				gomega.Expect(failure.Error()).To(gomega.Equal(expected))
			})
		})

		ginkgo.Describe("ID method", func() {
			ginkgo.It("returns the assigned ID", func() {
				gomega.Expect(failure.ID()).To(gomega.Equal(testID))
			})
		})

		ginkgo.Describe("Unwrap method", func() {
			ginkgo.It("returns the wrapped error", func() {
				gomega.Expect(failure.Unwrap()).To(gomega.Equal(wrappedErr))
			})
			ginkgo.It("returns nil when no wrapped error exists", func() {
				failureNoWrap := failures.Wrap(testMessage, testID, nil)
				gomega.Expect(failureNoWrap.Unwrap()).To(gomega.Succeed())
			})
		})

		ginkgo.Describe("Is method", func() {
			ginkgo.It("returns true for failures with the same ID", func() {
				f1 := failures.Wrap("first", testID, nil)
				f2 := failures.Wrap("second", testID, nil)
				gomega.Expect(f1.Is(f2)).To(gomega.BeTrue())
				gomega.Expect(f2.Is(f1)).To(gomega.BeTrue())
			})
			ginkgo.It("returns false for failures with different IDs", func() {
				f1 := failures.Wrap("first", testID, nil)
				f2 := failures.Wrap("second", testID+1, nil)
				gomega.Expect(f1.Is(f2)).To(gomega.BeFalse())
				gomega.Expect(f2.Is(f1)).To(gomega.BeFalse())
			})
			ginkgo.It("returns false when comparing with a non-failure error", func() {
				f1 := failures.Wrap("first", testID, nil)
				gomega.Expect(f1.Is(wrappedErr)).To(gomega.BeFalse())
			})
		})
	})

	ginkgo.Describe("edge cases", func() {
		ginkgo.When("wrapping with an empty message", func() {
			ginkgo.It("handles an empty message gracefully", func() {
				failure := failures.Wrap("", testID, wrappedErr)
				gomega.Expect(failure.Error()).To(gomega.Equal(": " + wrappedErr.Error()))
				gomega.Expect(failure.ID()).To(gomega.Equal(testID))
				gomega.Expect(failure.Unwrap()).To(gomega.Equal(wrappedErr))
			})
		})

		ginkgo.When("wrapping with nil error and no args", func() {
			ginkgo.It("returns a valid failure with just message and ID", func() {
				failure := failures.Wrap(testMessage, testID, nil)
				gomega.Expect(failure.Error()).To(gomega.Equal(testMessage))
				gomega.Expect(failure.ID()).To(gomega.Equal(testID))
				gomega.Expect(failure.Unwrap()).To(gomega.Succeed())
			})
		})

		ginkgo.When("using multiple wrapped failures", func() {
			ginkgo.It("correctly chains and unwraps multiple errors", func() {
				innerErr := errors.New("inner error")
				middleErr := failures.Wrap("middle", testID+1, innerErr)
				outerErr := failures.Wrap("outer", testID, middleErr)
				gomega.Expect(outerErr.Error()).To(gomega.Equal("outer: middle: inner error"))
				gomega.Expect(outerErr.ID()).To(gomega.Equal(testID))
				gomega.Expect(outerErr.Unwrap()).To(gomega.Equal(middleErr))
				gomega.Expect(middleErr.Unwrap()).To(gomega.Equal(innerErr))
			})
		})
	})

	ginkgo.Describe("integration-like scenarios", func() {
		ginkgo.It("works with standard error wrapping utilities", func() {
			innerErr := errors.New("inner error")
			failure := failures.Wrap("wrapped failure", testID, innerErr)
			gomega.Expect(errors.Is(failure, innerErr)).To(gomega.BeTrue()) // Matches wrapped error
			gomega.Expect(errors.Unwrap(failure)).To(gomega.Equal(innerErr))
		})

		ginkgo.It("handles fmt.Errorf wrapping", func() {
			failure := failures.Wrap("failure", testID, nil)
			wrapped := fmt.Errorf("additional context: %w", failure)
			gomega.Expect(wrapped.Error()).To(gomega.Equal("additional context: failure"))
			gomega.Expect(errors.Unwrap(wrapped)).To(gomega.Equal(failure))
		})
	})

	ginkgo.Describe("testutils integration", func() {
		ginkgo.It("can use TestLogger for logging failures", func() {
			// Demonstrate compatibility with testutils logger
			failure := failures.Wrap("logged failure", testID, nil)
			logger := testutils.TestLogger()
			logger.Printf("Error occurred: %v", failure)
			// No assertion needed; ensures no panic during logging
		})
	})
})
