package testingtproxy

import (
	"context"
	"fmt"
	"io"
	"os"

	"github.com/onsi/ginkgo/v2/formatter"
	"github.com/onsi/ginkgo/v2/internal"
	"github.com/onsi/ginkgo/v2/reporters"
	"github.com/onsi/ginkgo/v2/types"
)

type failFunc func(message string, callerSkip ...int)
type skipFunc func(message string, callerSkip ...int)
type cleanupFunc func(args ...any)
type reportFunc func() types.SpecReport
type addReportEntryFunc func(names string, args ...any)
type ginkgoWriterInterface interface {
	io.Writer

	Print(a ...any)
	Printf(format string, a ...any)
	Println(a ...any)
}
type ginkgoRecoverFunc func()
type attachProgressReporterFunc func(func() string) func()

var formatters = map[bool]formatter.Formatter{
	true:  formatter.NewWithNoColorBool(true),
	false: formatter.NewWithNoColorBool(false),
}

func New(writer ginkgoWriterInterface, fail failFunc, skip skipFunc, cleanup cleanupFunc, report reportFunc, addReportEntry addReportEntryFunc, ginkgoRecover ginkgoRecoverFunc, attachProgressReporter attachProgressReporterFunc, randomSeed int64, parallelProcess int, parallelTotal int, noColor bool, offset int) *ginkgoTestingTProxy {
	return &ginkgoTestingTProxy{
		fail:                   fail,
		offset:                 offset,
		writer:                 writer,
		skip:                   skip,
		cleanup:                cleanup,
		report:                 report,
		addReportEntry:         addReportEntry,
		ginkgoRecover:          ginkgoRecover,
		attachProgressReporter: attachProgressReporter,
		randomSeed:             randomSeed,
		parallelProcess:        parallelProcess,
		parallelTotal:          parallelTotal,
		f:                      formatters[noColor], //minimize allocations by reusing formatters
	}
}

type ginkgoTestingTProxy struct {
	fail                   failFunc
	skip                   skipFunc
	cleanup                cleanupFunc
	report                 reportFunc
	offset                 int
	writer                 ginkgoWriterInterface
	addReportEntry         addReportEntryFunc
	ginkgoRecover          ginkgoRecoverFunc
	attachProgressReporter attachProgressReporterFunc
	randomSeed             int64
	parallelProcess        int
	parallelTotal          int
	f                      formatter.Formatter
}

// basic testing.T support

func (t *ginkgoTestingTProxy) Cleanup(f func()) {
	t.cleanup(f, internal.Offset(1))
}

func (t *ginkgoTestingTProxy) Setenv(key, value string) {
	originalValue, exists := os.LookupEnv(key)
	if exists {
		t.cleanup(os.Setenv, key, originalValue, internal.Offset(1))
	} else {
		t.cleanup(os.Unsetenv, key, internal.Offset(1))
	}

	err := os.Setenv(key, value)
	if err != nil {
		t.fail(fmt.Sprintf("Failed to set environment variable: %v", err), 1)
	}
}

func (t *ginkgoTestingTProxy) Chdir(dir string) {
	currentDir, err := os.Getwd()
	if err != nil {
		t.fail(fmt.Sprintf("Failed to get current directory: %v", err), 1)
	}

	t.cleanup(os.Chdir, currentDir, internal.Offset(1))

	err = os.Chdir(dir)
	if err != nil {
		t.fail(fmt.Sprintf("Failed to change directory: %v", err), 1)
	}
}

func (t *ginkgoTestingTProxy) Context() context.Context {
	ctx, cancel := context.WithCancel(context.Background())
	t.cleanup(cancel, internal.Offset(1))
	return ctx
}

func (t *ginkgoTestingTProxy) Error(args ...any) {
	t.fail(fmt.Sprintln(args...), t.offset)
}

func (t *ginkgoTestingTProxy) Errorf(format string, args ...any) {
	t.fail(fmt.Sprintf(format, args...), t.offset)
}

func (t *ginkgoTestingTProxy) Fail() {
	t.fail("failed", t.offset)
}

func (t *ginkgoTestingTProxy) FailNow() {
	t.fail("failed", t.offset)
}

func (t *ginkgoTestingTProxy) Failed() bool {
	return t.report().Failed()
}

func (t *ginkgoTestingTProxy) Fatal(args ...any) {
	t.fail(fmt.Sprintln(args...), t.offset)
}

func (t *ginkgoTestingTProxy) Fatalf(format string, args ...any) {
	t.fail(fmt.Sprintf(format, args...), t.offset)
}

func (t *ginkgoTestingTProxy) Helper() {
	types.MarkAsHelper(1)
}

func (t *ginkgoTestingTProxy) Log(args ...any) {
	fmt.Fprintln(t.writer, args...)
}

func (t *ginkgoTestingTProxy) Logf(format string, args ...any) {
	t.Log(fmt.Sprintf(format, args...))
}

func (t *ginkgoTestingTProxy) Name() string {
	return t.report().FullText()
}

func (t *ginkgoTestingTProxy) Parallel() {
	// No-op
}

func (t *ginkgoTestingTProxy) Skip(args ...any) {
	t.skip(fmt.Sprintln(args...), t.offset)
}

func (t *ginkgoTestingTProxy) SkipNow() {
	t.skip("skip", t.offset)
}

func (t *ginkgoTestingTProxy) Skipf(format string, args ...any) {
	t.skip(fmt.Sprintf(format, args...), t.offset)
}

func (t *ginkgoTestingTProxy) Skipped() bool {
	return t.report().State.Is(types.SpecStateSkipped)
}

func (t *ginkgoTestingTProxy) TempDir() string {
	tmpDir, err := os.MkdirTemp("", "ginkgo")
	if err != nil {
		t.fail(fmt.Sprintf("Failed to create temporary directory: %v", err), 1)
		return ""
	}
	t.cleanup(os.RemoveAll, tmpDir)

	return tmpDir
}

// FullGinkgoTInterface
func (t *ginkgoTestingTProxy) AddReportEntryVisibilityAlways(name string, args ...any) {
	finalArgs := []any{internal.Offset(1), types.ReportEntryVisibilityAlways}
	t.addReportEntry(name, append(finalArgs, args...)...)
}
func (t *ginkgoTestingTProxy) AddReportEntryVisibilityFailureOrVerbose(name string, args ...any) {
	finalArgs := []any{internal.Offset(1), types.ReportEntryVisibilityFailureOrVerbose}
	t.addReportEntry(name, append(finalArgs, args...)...)
}
func (t *ginkgoTestingTProxy) AddReportEntryVisibilityNever(name string, args ...any) {
	finalArgs := []any{internal.Offset(1), types.ReportEntryVisibilityNever}
	t.addReportEntry(name, append(finalArgs, args...)...)
}
func (t *ginkgoTestingTProxy) Print(a ...any) {
	t.writer.Print(a...)
}
func (t *ginkgoTestingTProxy) Printf(format string, a ...any) {
	t.writer.Printf(format, a...)
}
func (t *ginkgoTestingTProxy) Println(a ...any) {
	t.writer.Println(a...)
}
func (t *ginkgoTestingTProxy) F(format string, args ...any) string {
	return t.f.F(format, args...)
}
func (t *ginkgoTestingTProxy) Fi(indentation uint, format string, args ...any) string {
	return t.f.Fi(indentation, format, args...)
}
func (t *ginkgoTestingTProxy) Fiw(indentation uint, maxWidth uint, format string, args ...any) string {
	return t.f.Fiw(indentation, maxWidth, format, args...)
}
func (t *ginkgoTestingTProxy) RenderTimeline() string {
	return reporters.RenderTimeline(t.report(), false)
}
func (t *ginkgoTestingTProxy) GinkgoRecover() {
	t.ginkgoRecover()
}
func (t *ginkgoTestingTProxy) DeferCleanup(args ...any) {
	finalArgs := []any{internal.Offset(1)}
	t.cleanup(append(finalArgs, args...)...)
}
func (t *ginkgoTestingTProxy) RandomSeed() int64 {
	return t.randomSeed
}
func (t *ginkgoTestingTProxy) ParallelProcess() int {
	return t.parallelProcess
}
func (t *ginkgoTestingTProxy) ParallelTotal() int {
	return t.parallelTotal
}
func (t *ginkgoTestingTProxy) AttachProgressReporter(f func() string) func() {
	return t.attachProgressReporter(f)
}
func (t *ginkgoTestingTProxy) Output() io.Writer {
	return t.writer
}
func (t *ginkgoTestingTProxy) Attr(key, value string) {
	t.addReportEntry(key, value, internal.Offset(1), types.ReportEntryVisibilityFailureOrVerbose)
}
