package container

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"io/fs"
	"log"
	"os"
	"os/exec"
	"reflect"
	"runtime"
	"slices"
	"strings"
	"syscall"
	"testing"
	"time"

	"hakurei.app/container/seccomp"
	"hakurei.app/container/std"
	"hakurei.app/internal/stub"
	"hakurei.app/message"
)

type opValidTestCase struct {
	name string
	op   Op
	want bool
}

func checkOpsValid(t *testing.T, testCases []opValidTestCase) {
	t.Helper()

	t.Run("valid", func(t *testing.T) {
		t.Helper()
		t.Parallel()

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				t.Helper()
				t.Parallel()

				if got := tc.op.Valid(); got != tc.want {
					t.Errorf("Valid: %v, want %v", got, tc.want)
				}
			})
		}
	})
}

type opsBuilderTestCase struct {
	name string
	ops  *Ops
	want Ops
}

func checkOpsBuilder(t *testing.T, testCases []opsBuilderTestCase) {
	t.Helper()

	t.Run("build", func(t *testing.T) {
		t.Helper()
		t.Parallel()

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				t.Helper()
				t.Parallel()

				if !slices.EqualFunc(*tc.ops, tc.want, func(op Op, v Op) bool { return op.Is(v) }) {
					t.Errorf("Ops: %#v, want %#v", tc.ops, tc.want)
				}
			})
		}
	})
}

type opIsTestCase struct {
	name  string
	op, v Op
	want  bool
}

func checkOpIs(t *testing.T, testCases []opIsTestCase) {
	t.Helper()

	t.Run("is", func(t *testing.T) {
		t.Helper()
		t.Parallel()

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				t.Helper()
				t.Parallel()

				if got := tc.op.Is(tc.v); got != tc.want {
					t.Errorf("Is: %v, want %v", got, tc.want)
				}
			})
		}
	})
}

type opMetaTestCase struct {
	name string
	op   Op

	wantPrefix string
	wantString string
}

func checkOpMeta(t *testing.T, testCases []opMetaTestCase) {
	t.Helper()

	t.Run("meta", func(t *testing.T) {
		t.Helper()
		t.Parallel()

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				t.Helper()
				t.Parallel()

				t.Run("prefix", func(t *testing.T) {
					t.Helper()

					if got, _ := tc.op.prefix(); got != tc.wantPrefix {
						t.Errorf("prefix: %q, want %q", got, tc.wantPrefix)
					}
				})

				t.Run("string", func(t *testing.T) {
					t.Helper()

					if got := tc.op.String(); got != tc.wantString {
						t.Errorf("String: %s, want %s", got, tc.wantString)
					}
				})
			})
		}
	})
}

// call initialises a [stub.Call].
// This keeps composites analysis happy without making the test cases too bloated.
func call(name string, args stub.ExpectArgs, ret any, err error) stub.Call {
	return stub.Call{Name: name, Args: args, Ret: ret, Err: err}
}

type simpleTestCase struct {
	name    string
	f       func(k *kstub) error
	want    stub.Expect
	wantErr error
}

func checkSimple(t *testing.T, fname string, testCases []simpleTestCase) {
	t.Helper()

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			t.Helper()
			t.Parallel()

			wait4signal := make(chan struct{})
			lockNotify := make(chan struct{})
			k := &kstub{wait4signal, lockNotify, stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{wait4signal, lockNotify, s} }, tc.want)}
			defer stub.HandleExit(t)
			if err := tc.f(k); !reflect.DeepEqual(err, tc.wantErr) {
				t.Errorf("%s: error = %v, want %v", fname, err, tc.wantErr)
			}
			k.VisitIncomplete(func(s *stub.Stub[syscallDispatcher]) {
				t.Helper()

				t.Errorf("%s: %d calls, want %d", fname, s.Pos(), s.Len())
			})
		})
	}
}

type opBehaviourTestCase struct {
	name   string
	params *Params
	op     Op

	early        []stub.Call
	wantErrEarly error

	apply        []stub.Call
	wantErrApply error
}

func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
	t.Helper()

	t.Run("behaviour", func(t *testing.T) {
		t.Helper()
		t.Parallel()

		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				t.Helper()
				t.Parallel()

				k := &kstub{nil, nil, stub.New(t,
					func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{nil, nil, s} },
					stub.Expect{Calls: slices.Concat(tc.early, []stub.Call{{Name: stub.CallSeparator}}, tc.apply)},
				)}
				state := &setupState{Params: tc.params, Msg: k}
				defer stub.HandleExit(t)
				errEarly := tc.op.early(state, k)
				k.Expects(stub.CallSeparator)
				if !reflect.DeepEqual(errEarly, tc.wantErrEarly) {
					t.Errorf("early: error = %v, want %v", errEarly, tc.wantErrEarly)
				}
				if errEarly != nil {
					goto out
				}

				if err := tc.op.apply(state, k); !reflect.DeepEqual(err, tc.wantErrApply) {
					t.Errorf("apply: error = %v, want %v", err, tc.wantErrApply)
				}

			out:
				k.VisitIncomplete(func(s *stub.Stub[syscallDispatcher]) {
					count := k.Pos() - 1 // separator
					if count < len(tc.early) {
						t.Errorf("early: %d calls, want %d", count, len(tc.early))
					} else {
						t.Errorf("apply: %d calls, want %d", count-len(tc.early), len(tc.apply))
					}
				})
			})
		}
	})
}

func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile {
	f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr}
	// check happens in Close, and cleanup is not guaranteed to run, so relying
	// on it for sloppy implementations will cause sporadic test results
	f.cleanup = runtime.AddCleanup(f, func(name string) {
		panic("checkedOsFile " + name + " became unreachable without a call to Close")
	}, name)
	return f
}

type checkedOsFile struct {
	t        *testing.T
	name     string
	want     string
	closeErr error
	cleanup  runtime.Cleanup
	bytes.Buffer
}

func (f *checkedOsFile) Name() string               { return f.name }
func (f *checkedOsFile) Stat() (fs.FileInfo, error) { panic("unreachable") }
func (f *checkedOsFile) Close() error {
	defer f.cleanup.Stop()
	if f.String() != f.want {
		f.t.Errorf("checkedOsFile:\n%s\nwant\n%s", f.String(), f.want)
		return syscall.ENOTRECOVERABLE
	}
	return f.closeErr
}

func newConstFile(s string) osFile { return &readerOsFile{Reader: strings.NewReader(s)} }

type readerOsFile struct {
	closed bool
	io.Reader
}

func (*readerOsFile) Name() string               { panic("unreachable") }
func (*readerOsFile) Write([]byte) (int, error)  { panic("unreachable") }
func (*readerOsFile) Stat() (fs.FileInfo, error) { panic("unreachable") }
func (r *readerOsFile) Close() error {
	if r.closed {
		return os.ErrClosed
	}
	r.closed = true
	return nil
}

type writeErrOsFile struct{ err error }

func (writeErrOsFile) Name() string                { panic("unreachable") }
func (f writeErrOsFile) Write([]byte) (int, error) { return 0, f.err }
func (writeErrOsFile) Stat() (fs.FileInfo, error)  { panic("unreachable") }
func (writeErrOsFile) Read([]byte) (int, error)    { panic("unreachable") }
func (writeErrOsFile) Close() error                { panic("unreachable") }

type isDirFi bool

func (isDirFi) Name() string       { panic("unreachable") }
func (isDirFi) Size() int64        { panic("unreachable") }
func (isDirFi) Mode() fs.FileMode  { panic("unreachable") }
func (isDirFi) ModTime() time.Time { panic("unreachable") }
func (fi isDirFi) IsDir() bool     { return bool(fi) }
func (isDirFi) Sys() any           { panic("unreachable") }

func stubDir(names ...string) []os.DirEntry {
	d := make([]os.DirEntry, len(names))
	for i, name := range names {
		d[i] = nameDentry(name)
	}
	return d
}

type nameDentry string

func (e nameDentry) Name() string             { return string(e) }
func (nameDentry) IsDir() bool                { panic("unreachable") }
func (nameDentry) Type() fs.FileMode          { panic("unreachable") }
func (nameDentry) Info() (fs.FileInfo, error) { panic("unreachable") }

const (
	// magicWait4Signal must be used in a single pair of signal and wait4 calls across two goroutines
	// originating from the same toplevel kstub.
	// To enable this behaviour this value must be the last element of the args field in the wait4 call
	// and the ret value of the signal call.
	magicWait4Signal = 0xdef
)

type kstub struct {
	wait4signal chan struct{}
	lockNotify  chan struct{}
	*stub.Stub[syscallDispatcher]
}

func (k *kstub) new(f func(k syscallDispatcher)) { k.Helper(); k.New(f) }

func (k *kstub) lockOSThread() {
	k.Helper()
	expect := k.Expects("lockOSThread")
	if k.lockNotify != nil && expect.Ret == magicWait4Signal {
		<-k.lockNotify
	}
}

func (k *kstub) setPtracer(pid uintptr) error {
	k.Helper()
	return k.Expects("setPtracer").Error(
		stub.CheckArg(k.Stub, "pid", pid, 0))
}

func (k *kstub) setDumpable(dumpable uintptr) error {
	k.Helper()
	return k.Expects("setDumpable").Error(
		stub.CheckArg(k.Stub, "dumpable", dumpable, 0))
}

func (k *kstub) setNoNewPrivs() error { k.Helper(); return k.Expects("setNoNewPrivs").Err }
func (k *kstub) lastcap(msg message.Msg) uintptr {
	k.Helper()
	k.checkMsg(msg)
	return k.Expects("lastcap").Ret.(uintptr)
}

func (k *kstub) capset(hdrp *capHeader, datap *[2]capData) error {
	k.Helper()
	return k.Expects("capset").Error(
		stub.CheckArgReflect(k.Stub, "hdrp", hdrp, 0),
		stub.CheckArgReflect(k.Stub, "datap", datap, 1))
}

func (k *kstub) capBoundingSetDrop(cap uintptr) error {
	k.Helper()
	return k.Expects("capBoundingSetDrop").Error(
		stub.CheckArg(k.Stub, "cap", cap, 0))
}

func (k *kstub) capAmbientClearAll() error { k.Helper(); return k.Expects("capAmbientClearAll").Err }

func (k *kstub) capAmbientRaise(cap uintptr) error {
	k.Helper()
	return k.Expects("capAmbientRaise").Error(
		stub.CheckArg(k.Stub, "cap", cap, 0))
}

func (k *kstub) isatty(fd int) bool {
	k.Helper()
	expect := k.Expects("isatty")
	if !stub.CheckArg(k.Stub, "fd", fd, 0) {
		k.FailNow()
	}
	return expect.Ret.(bool)
}

func (k *kstub) receive(key string, e any, fdp *int) (closeFunc func() error, err error) {
	k.Helper()
	expect := k.Expects("receive")

	var closed bool
	closeFunc = func() error {
		if closed {
			k.Error("closeFunc called more than once")
			return os.ErrClosed
		}
		closed = true

		if expect.Ret != nil {
			// use return stored in kexpect for closeFunc instead
			return expect.Ret.(error)
		}
		return nil
	}

	// avoid changing test cases
	var fdpComp *uintptr
	if fdp != nil {
		fdpComp = new(uintptr(*fdp))
	}

	err = expect.Error(
		stub.CheckArg(k.Stub, "key", key, 0),
		stub.CheckArgReflect(k.Stub, "e", e, 1),
		stub.CheckArgReflect(k.Stub, "fdp", fdpComp, 2))

	// 3 is unused so stores params
	if expect.Args[3] != nil {
		if v, ok := expect.Args[3].(*initParams); ok && v != nil {
			if p, ok0 := e.(*initParams); ok0 && p != nil {
				*p = *v
			}
		}
	}

	// 4 is unused so stores fd
	if expect.Args[4] != nil {
		if v, ok := expect.Args[4].(uintptr); ok && v >= 3 {
			if fdp != nil {
				*fdp = int(v)
			}
		}
	}

	return
}

func (k *kstub) bindMount(msg message.Msg, source, target string, flags uintptr) error {
	k.Helper()
	k.checkMsg(msg)
	return k.Expects("bindMount").Error(
		stub.CheckArg(k.Stub, "source", source, 0),
		stub.CheckArg(k.Stub, "target", target, 1),
		stub.CheckArg(k.Stub, "flags", flags, 2))
}

func (k *kstub) remount(msg message.Msg, target string, flags uintptr) error {
	k.Helper()
	k.checkMsg(msg)
	return k.Expects("remount").Error(
		stub.CheckArg(k.Stub, "target", target, 0),
		stub.CheckArg(k.Stub, "flags", flags, 1))
}

func (k *kstub) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
	k.Helper()
	return k.Expects("mountTmpfs").Error(
		stub.CheckArg(k.Stub, "fsname", fsname, 0),
		stub.CheckArg(k.Stub, "target", target, 1),
		stub.CheckArg(k.Stub, "flags", flags, 2),
		stub.CheckArg(k.Stub, "size", size, 3),
		stub.CheckArg(k.Stub, "perm", perm, 4))
}

func (k *kstub) mountOverlay(target string, options [][2]string) error {
	k.Helper()
	return k.Expects("mountOverlay").Error(
		stub.CheckArg(k.Stub, "target", target, 0),
		stub.CheckArgReflect(k.Stub, "options", options, 1),
	)
}

func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error {
	k.Helper()
	return k.Expects("ensureFile").Error(
		stub.CheckArg(k.Stub, "name", name, 0),
		stub.CheckArg(k.Stub, "perm", perm, 1),
		stub.CheckArg(k.Stub, "pperm", pperm, 2))
}

func (*kstub) mustLoopback(context.Context, message.Msg) { /* noop */ }

func (k *kstub) seccompLoad(rules []std.NativeRule, flags seccomp.ExportFlag) error {
	k.Helper()
	return k.Expects("seccompLoad").Error(
		stub.CheckArgReflect(k.Stub, "rules", rules, 0),
		stub.CheckArg(k.Stub, "flags", flags, 1))
}

func (k *kstub) notify(c chan<- os.Signal, sig ...os.Signal) {
	k.Helper()
	expect := k.Expects("notify")
	if c == nil || expect.Error(
		stub.CheckArgReflect(k.Stub, "sig", sig, 1)) != nil {
		k.FailNow()
	}

	if k.lockNotify != nil && expect.Ret == magicWait4Signal {
		defer close(k.lockNotify)
	}

	// export channel for external instrumentation
	if chanf, ok := expect.Args[0].(func(c chan<- os.Signal)); ok && chanf != nil {
		chanf(c)
	}
}

func (k *kstub) start(c *exec.Cmd) error {
	k.Helper()
	expect := k.Expects("start")
	err := expect.Error(
		stub.CheckArg(k.Stub, "c.Path", c.Path, 0),
		stub.CheckArgReflect(k.Stub, "c.Args", c.Args, 1),
		stub.CheckArgReflect(k.Stub, "c.Env", c.Env, 2),
		stub.CheckArg(k.Stub, "c.Dir", c.Dir, 3))

	if process, ok := expect.Ret.(*os.Process); ok && process != nil {
		c.Process = process
	}
	return err
}

func (k *kstub) signal(c *exec.Cmd, sig os.Signal) error {
	k.Helper()
	expect := k.Expects("signal")
	if v, ok := expect.Ret.(int); ok && v == magicWait4Signal {
		if k.wait4signal == nil {
			panic("kstub not initialised for wait4 simulation")
		}
		defer func() { close(k.wait4signal) }()
	}
	return expect.Error(
		stub.CheckArg(k.Stub, "c.Path", c.Path, 0),
		stub.CheckArgReflect(k.Stub, "c.Args", c.Args, 1),
		stub.CheckArgReflect(k.Stub, "c.Env", c.Env, 2),
		stub.CheckArg(k.Stub, "c.Dir", c.Dir, 3),
		stub.CheckArg(k.Stub, "sig", sig, 4))
}

func (k *kstub) evalSymlinks(path string) (string, error) {
	k.Helper()
	expect := k.Expects("evalSymlinks")
	return expect.Ret.(string), expect.Error(
		stub.CheckArg(k.Stub, "path", path, 0))
}

func (k *kstub) exit(code int) {
	k.Helper()
	k.Expects("exit")
	if !stub.CheckArg(k.Stub, "code", code, 0) {
		k.FailNow()
	}
	panic(stub.PanicExit)
}

func (k *kstub) getpid() int { k.Helper(); return k.Expects("getpid").Ret.(int) }

func (k *kstub) stat(name string) (os.FileInfo, error) {
	k.Helper()
	expect := k.Expects("stat")
	return expect.Ret.(os.FileInfo), expect.Error(
		stub.CheckArg(k.Stub, "name", name, 0))
}

func (k *kstub) mkdir(name string, perm os.FileMode) error {
	k.Helper()
	return k.Expects("mkdir").Error(
		stub.CheckArg(k.Stub, "name", name, 0),
		stub.CheckArg(k.Stub, "perm", perm, 1))
}

func (k *kstub) mkdirTemp(dir, pattern string) (string, error) {
	k.Helper()
	expect := k.Expects("mkdirTemp")
	return expect.Ret.(string), expect.Error(
		stub.CheckArg(k.Stub, "dir", dir, 0),
		stub.CheckArg(k.Stub, "pattern", pattern, 1))
}

func (k *kstub) mkdirAll(path string, perm os.FileMode) error {
	k.Helper()
	return k.Expects("mkdirAll").Error(
		stub.CheckArg(k.Stub, "path", path, 0),
		stub.CheckArg(k.Stub, "perm", perm, 1))
}

func (k *kstub) readdir(name string) ([]os.DirEntry, error) {
	k.Helper()
	expect := k.Expects("readdir")
	return expect.Ret.([]os.DirEntry), expect.Error(
		stub.CheckArg(k.Stub, "name", name, 0))
}

func (k *kstub) openNew(name string) (osFile, error) {
	k.Helper()
	expect := k.Expects("openNew")
	return expect.Ret.(osFile), expect.Error(
		stub.CheckArg(k.Stub, "name", name, 0))
}

func (k *kstub) writeFile(name string, data []byte, perm os.FileMode) error {
	k.Helper()
	return k.Expects("writeFile").Error(
		stub.CheckArg(k.Stub, "name", name, 0),
		stub.CheckArgReflect(k.Stub, "data", data, 1),
		stub.CheckArg(k.Stub, "perm", perm, 2))
}

func (k *kstub) createTemp(dir, pattern string) (osFile, error) {
	k.Helper()
	expect := k.Expects("createTemp")
	return expect.Ret.(osFile), expect.Error(
		stub.CheckArg(k.Stub, "dir", dir, 0),
		stub.CheckArg(k.Stub, "pattern", pattern, 1))
}

func (k *kstub) remove(name string) error {
	k.Helper()
	return k.Expects("remove").Error(
		stub.CheckArg(k.Stub, "name", name, 0))
}

func (k *kstub) newFile(fd uintptr, name string) *os.File {
	k.Helper()
	expect := k.Expects("newFile")
	if expect.Error(
		stub.CheckArg(k.Stub, "fd", fd, 0),
		stub.CheckArg(k.Stub, "name", name, 1)) != nil {
		k.FailNow()
	}
	return expect.Ret.(*os.File)
}

func (k *kstub) symlink(oldname, newname string) error {
	k.Helper()
	return k.Expects("symlink").Error(
		stub.CheckArg(k.Stub, "oldname", oldname, 0),
		stub.CheckArg(k.Stub, "newname", newname, 1))
}

func (k *kstub) readlink(name string) (string, error) {
	k.Helper()
	expect := k.Expects("readlink")
	return expect.Ret.(string), expect.Error(
		stub.CheckArg(k.Stub, "name", name, 0))
}

func (k *kstub) umask(mask int) (oldmask int) {
	k.Helper()
	expect := k.Expects("umask")
	if !stub.CheckArg(k.Stub, "mask", mask, 0) {
		k.FailNow()
	}
	return expect.Ret.(int)
}

func (k *kstub) sethostname(p []byte) (err error) {
	k.Helper()
	return k.Expects("sethostname").Error(
		stub.CheckArgReflect(k.Stub, "p", p, 0))
}

func (k *kstub) chdir(path string) (err error) {
	k.Helper()
	return k.Expects("chdir").Error(
		stub.CheckArg(k.Stub, "path", path, 0))
}

func (k *kstub) fchdir(fd int) (err error) {
	k.Helper()
	return k.Expects("fchdir").Error(
		stub.CheckArg(k.Stub, "fd", fd, 0))
}

func (k *kstub) open(path string, mode int, perm uint32) (fd int, err error) {
	k.Helper()
	expect := k.Expects("open")
	return expect.Ret.(int), expect.Error(
		stub.CheckArg(k.Stub, "path", path, 0),
		stub.CheckArg(k.Stub, "mode", mode, 1),
		stub.CheckArg(k.Stub, "perm", perm, 2))
}

func (k *kstub) close(fd int) (err error) {
	k.Helper()
	return k.Expects("close").Error(
		stub.CheckArg(k.Stub, "fd", fd, 0))
}

func (k *kstub) pivotRoot(newroot, putold string) (err error) {
	k.Helper()
	return k.Expects("pivotRoot").Error(
		stub.CheckArg(k.Stub, "newroot", newroot, 0),
		stub.CheckArg(k.Stub, "putold", putold, 1))
}

func (k *kstub) mount(source, target, fstype string, flags uintptr, data string) (err error) {
	k.Helper()
	return k.Expects("mount").Error(
		stub.CheckArg(k.Stub, "source", source, 0),
		stub.CheckArg(k.Stub, "target", target, 1),
		stub.CheckArg(k.Stub, "fstype", fstype, 2),
		stub.CheckArg(k.Stub, "flags", flags, 3),
		stub.CheckArg(k.Stub, "data", data, 4))
}

func (k *kstub) unmount(target string, flags int) (err error) {
	k.Helper()
	return k.Expects("unmount").Error(
		stub.CheckArg(k.Stub, "target", target, 0),
		stub.CheckArg(k.Stub, "flags", flags, 1))
}

func (k *kstub) wait4(pid int, wstatus *syscall.WaitStatus, options int, rusage *syscall.Rusage) (wpid int, err error) {
	k.Helper()
	expect := k.Expects("wait4")
	if v, ok := expect.Args[4].(int); ok {
		switch v {
		case stub.PanicExit: // special case to prevent leaking the wait4 goroutine while testing initEntrypoint
			panic(stub.PanicExit)

		case magicWait4Signal: // block until corresponding signal call
			if k.wait4signal == nil {
				panic("kstub not initialised for wait4 simulation")
			}
			<-k.wait4signal
		}
	}

	wpid = expect.Ret.(int)
	err = expect.Error(
		stub.CheckArg(k.Stub, "pid", pid, 0),
		stub.CheckArg(k.Stub, "options", options, 2))

	if wstatusV, ok := expect.Args[1].(syscall.WaitStatus); wstatus != nil && ok {
		*wstatus = wstatusV
	}
	if rusageV, ok := expect.Args[3].(syscall.Rusage); rusage != nil && ok {
		*rusage = rusageV
	}

	return
}

func (k *kstub) printf(_ message.Msg, format string, v ...any) {
	k.Helper()
	if k.Expects("printf").Error(
		stub.CheckArg(k.Stub, "format", format, 0),
		stub.CheckArgReflect(k.Stub, "v", v, 1)) != nil {
		k.FailNow()
	}
}

func (k *kstub) fatal(_ message.Msg, v ...any) {
	k.Helper()
	if k.Expects("fatal").Error(
		stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
		k.FailNow()
	}
	panic(stub.PanicExit)
}

func (k *kstub) fatalf(_ message.Msg, format string, v ...any) {
	k.Helper()
	if k.Expects("fatalf").Error(
		stub.CheckArg(k.Stub, "format", format, 0),
		stub.CheckArgReflect(k.Stub, "v", v, 1)) != nil {
		k.FailNow()
	}
	panic(stub.PanicExit)
}

func (k *kstub) checkMsg(msg message.Msg) {
	k.Helper()
	var target *kstub

	if state, ok := msg.(*setupState); ok {
		target = state.Msg.(*kstub)
	} else {
		target = msg.(*kstub)
	}

	if k != target {
		panic(fmt.Sprintf("unexpected Msg: %#v", msg))
	}
}

func (k *kstub) GetLogger() *log.Logger { panic("unreachable") }

func (k *kstub) IsVerbose() bool { k.Helper(); return k.Expects("isVerbose").Ret.(bool) }

func (k *kstub) SwapVerbose(verbose bool) bool {
	k.Helper()
	expect := k.Expects("swapVerbose")
	if expect.Error(
		stub.CheckArg(k.Stub, "verbose", verbose, 0)) != nil {
		k.FailNow()
	}
	return expect.Ret.(bool)
}

func (k *kstub) Verbose(v ...any) {
	k.Helper()
	if k.Expects("verbose").Error(
		stub.CheckArgReflect(k.Stub, "v", v, 0)) != nil {
		k.FailNow()
	}
}

func (k *kstub) Verbosef(format string, v ...any) {
	k.Helper()
	if k.Expects("verbosef").Error(
		stub.CheckArg(k.Stub, "format", format, 0),
		stub.CheckArgReflect(k.Stub, "v", v, 1)) != nil {
		k.FailNow()
	}
}

func (k *kstub) Suspend() bool { k.Helper(); return k.Expects("suspend").Ret.(bool) }
func (k *kstub) Resume() bool  { k.Helper(); return k.Expects("resume").Ret.(bool) }
func (k *kstub) BeforeExit()   { k.Helper(); k.Expects("beforeExit") }
