// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

// Package gmatcher provides custom Gomega matchers.
package gmatcher

import (
	"fmt"
	"strings"

	"github.com/onsi/gomega"

	"github.com/apache/skywalking-banyandb/pkg/pool"
)

// HaveZeroRef returns a matcher that checks if all pools have 0 references.
func HaveZeroRef() gomega.OmegaMatcher {
	return &ZeroRefMatcher{}
}

var _ gomega.OmegaMatcher = &ZeroRefMatcher{}

// ZeroRefMatcher is a matcher that checks if all pools have 0 references.
type ZeroRefMatcher struct{}

// FailureMessage implements types.GomegaMatcher.
func (p *ZeroRefMatcher) FailureMessage(actual interface{}) (message string) {
	data, ok := actual.(map[string]int)
	if !ok {
		return fmt.Sprintf("expected all pools to have 0 references, got %v", actual)
	}

	// Get stack traces for leaked pools
	allStacks := pool.AllStacks()

	var leakedPools []string
	for poolName, refs := range data {
		if refs > 0 {
			leakedPools = append(leakedPools, fmt.Sprintf("%s: %d refs", poolName, refs))
		}
	}

	var sb strings.Builder
	sb.WriteString("expected all pools to have 0 references, but found leaks:\n")
	sb.WriteString(strings.Join(leakedPools, ", "))
	sb.WriteString("\n\n")

	// Include stack traces for leaked pools
	if len(allStacks) > 0 {
		sb.WriteString("Stack traces for leaked objects:\n")
		for poolName, stacks := range allStacks {
			if refs, exists := data[poolName]; exists && refs > 0 {
				sb.WriteString(fmt.Sprintf("\n========== Pool: %s (Count: %d) ==========\n", poolName, len(stacks)))
				for i, stack := range stacks {
					sb.WriteString(fmt.Sprintf("\n--- Stack #%d ---\n%s\n", i+1, stack))
				}
			}
		}
	}

	return sb.String()
}

// Match implements types.GomegaMatcher.
func (p *ZeroRefMatcher) Match(actual interface{}) (success bool, err error) {
	data, ok := actual.(map[string]int)
	if !ok {
		return false, fmt.Errorf("expected map[string]int, got %T", actual)
	}
	for _, refers := range data {
		if refers > 0 {
			return false, nil
		}
	}
	return true, nil
}

// NegatedFailureMessage implements types.GomegaMatcher.
func (p *ZeroRefMatcher) NegatedFailureMessage(actual interface{}) (message string) {
	return fmt.Sprintf("expected at least one pool to have references, got %v", actual)
}
