// SPDX-FileCopyrightText: Copyright 2025 go-swagger maintainers
// SPDX-License-Identifier: Apache-2.0

package assertions

import (
	"errors"
	"io"
	"iter"
	"slices"
	"strings"
	"testing"
)

func TestPanicDidPanic(t *testing.T) {
	t.Parallel()

	const panicMsg = "Panic!"

	if funcDidPanic, msg, _ := didPanic(func() {
		panic(panicMsg)
	}); !funcDidPanic || msg != panicMsg {
		t.Error("didPanic should return true, panicMsg")
	}

	{
		funcDidPanic, msg, _ := didPanic(func() {
			err := errors.New("test")
			panic(err)
		})
		if !funcDidPanic {
			t.Error("didPanic should have panicked")
		}
		if msg == nil {
			t.Errorf("didPanic should have returned something, but got nil")
		}
	}

	{
		// Go 1.21+ wraps panic(nil) as runtime.PanicNilError;
		// didPanic normalizes the message back to nil.
		funcDidPanic, msg, _ := didPanic(func() {
			panic(nil) //nolint:nilness // deliberate: testing the panic(nil) edge case
		})
		if !funcDidPanic {
			t.Error("didPanic should have panicked on panic(nil)")
		}
		if msg != nil {
			t.Errorf("didPanic should have normalized panic(nil) message to nil, got %v", msg)
		}
	}

	if funcDidPanic, _, _ := didPanic(func() {
	}); funcDidPanic {
		t.Error("didPanic should return false")
	}
}

func TestPanics(t *testing.T) {
	t.Parallel()
	mock := new(mockT)

	if !Panics(mock, func() {
		panic("Panic!")
	}) {
		t.Error("Panics should return true")
	}

	if Panics(mock, func() {
	}) {
		t.Error("Panics should return false")
	}
}

func TestPanicsWithValue(t *testing.T) {
	t.Parallel()
	mock := new(mockT)

	if !PanicsWithValue(mock, "Panic!", func() {
		panic("Panic!")
	}) {
		t.Error("PanicsWithValue should return true")
	}

	{
		err := errors.New("test")
		if !PanicsWithValue(mock, err, func() {
			panic(err) // panic no longer supports a nil argument
		}) {
			t.Error("PanicsWithValue should return true")
		}
	}

	if PanicsWithValue(mock, "Panic!", func() {
	}) {
		t.Error("PanicsWithValue should return false")
	}

	if PanicsWithValue(mock, "at the disco", func() {
		panic("Panic!")
	}) {
		t.Error("PanicsWithValue should return false")
	}
}

func TestPanicsWithError(t *testing.T) {
	t.Parallel()

	mock := new(mockT)
	succeeded := PanicsWithError(mock, "panic", func() {
		panic(errors.New("panic"))
	})
	shouldPassOrFail(t, mock, succeeded, true)
}

func TestPanicNotPanics(t *testing.T) {
	t.Parallel()
	mock := new(mockT)

	if !NotPanics(mock, func() {
	}) {
		t.Error("NotPanics should return true")
	}

	if NotPanics(mock, func() {
		panic("Panic!")
	}) {
		t.Error("NotPanics should return false")
	}
}

func TestPanicCallerInfoWithAutogeneratedFunctions(t *testing.T) {
	t.Parallel()

	func() {
		defer func() {
			if r := recover(); r != nil {
				t.Errorf("testAutogeneratedFunction should not panic: %v", r)
			}
		}()
		testAutogeneratedFunction()
	}()
}

func TestPanicsWithErrorMessages(t *testing.T) {
	t.Parallel()

	runFailCases(t, panicsWithErrorFailCases())

	t.Run("string-panic-has-no-error-message-label", func(t *testing.T) {
		t.Parallel()

		mock := new(mockT)
		PanicsWithError(mock, "expected panic msg", func() {
			panic("actual panic msg")
		})
		if strings.Contains(mock.errorString(), "Error message:") {
			t.Error("PanicsWithError should not report error message if not due an error")
		}
	})
}

// =======================================
// TestPanicsWithErrorMessages
// =======================================

func panicsWithErrorFailCases() iter.Seq[failCase] {
	return slices.Values([]failCase{
		{
			name:         "PanicsWithError/no-panic",
			assertion:    func(t T) bool { return PanicsWithError(t, "Panic!", func() {}) },
			wantContains: []string{"Panic value:"},
		},
		{
			name: "PanicsWithError/wrong-error",
			assertion: func(t T) bool {
				return PanicsWithError(t, "expected panic err msg", func() {
					panic(errors.New("actual panic err msg"))
				})
			},
			wantContains: []string{"Error message:", "actual panic err msg"},
		},
		{
			name: "PanicsWithError/wrapped-error",
			assertion: func(t T) bool {
				return PanicsWithError(t, "expected panic err msg", func() {
					panic(&PanicsWrapperError{"wrapped", errors.New("actual panic err msg")})
				})
			},
			wantContains: []string{"Error message:", "wrapped: actual panic err msg"},
		},
		{
			name: "PanicsWithError/string-panic",
			assertion: func(t T) bool {
				return PanicsWithError(t, "expected panic msg", func() {
					panic("actual panic msg")
				})
			},
			wantContains: []string{"Panic value:", "actual panic msg"},
		},
	})
}

type PanicsWrapperError struct {
	Prefix string
	Err    error
}

func (e PanicsWrapperError) Error() string {
	return e.Prefix + ": " + e.Err.Error()
}

func testAutogeneratedFunction() {
	defer func() {
		if err := recover(); err == nil {
			panic("did not panic")
		}
		CallerInfo()
	}()
	t := struct {
		io.Closer
	}{}
	c := t
	c.Close()
}
