package suite

import (
	"bufio"
	"context"
	"os"

	"code.justin.tv/eventbus/controlplane/e2e/internal/e2eutil"
	"code.justin.tv/eventbus/controlplane/e2e/internal/report"
	"code.justin.tv/eventbus/controlplane/e2e/internal/test"
	"code.justin.tv/eventbus/controlplane/internal/uuid"
)

// TODO: context methods and With in suite.Run
const PhaseSetup = "setup"
const PhaseTest = "test"
const PhaseClean = "clean"

// Runner is like a test.Runner but also has its own top level name
// and mechanism for providing error reporting
type Runner interface {
	test.Runner
	SuiteName() string
	Errors() []report.Error
	// TODO: MOVE METHOD TO GET ALL ERRORS HERE
	// AND HAVE ReportError METHOD IN test.Runner INSTEAD
	// THAT WILL BUBBLE UP HERE
}

// Run will run a test suite. If there are any failures during setup,
// Run will immediately return the error. If there are no errors during testing, errors
// during Clean will be reported (if any). If there are errors during Test, those will be aggregated
// together will any error from Clean and returned
func Run(ctx context.Context, s Runner, cfg *test.RuntimeConfig) {
	if !cfg.ShouldRun(s.SuiteName()) {
		return
	}

	jobID, err := uuid.NewUUID()
	if err != nil {
		report.LogErrorsFatally([]report.Error{report.ErrorFromContext(ctx, "Could not create JobID", err)})
	}

	ctx = e2eutil.WithSuiteName(ctx, s.SuiteName())
	ctx = e2eutil.WithJobID(ctx, jobID)

	setupErr := s.Setup(e2eutil.WithPhase(ctx, "setup"))
	if setupErr != nil {
		setupErrs := []report.Error{setupErr}
		err := e2eutil.TruncateDB()
		if err != nil {
			// Tack on this new error when truncating
			setupErrs = append(setupErrs, report.ErrorFromContext(ctx, "Could not truncate database", err))
		}
		report.LogErrorsFatally(setupErrs)
	}

	s.Test(e2eutil.WithPhase(ctx, "test"))
	testErrs := s.Errors()

	if cfg.PromptBeforeClean {
		s.Log(ctx, "Execution paused before cleaning for debugging purposes. Press 'Enter' to resume")
		_, err := bufio.NewReader(os.Stdin).ReadBytes('\n')
		if err != nil {
			panic(err) // something really bad happened somehow
		}
	}
	cleanErrs := s.Clean(e2eutil.WithPhase(ctx, "clean"))

	totalErrs := make([]report.Error, 0)
	if testErrs != nil {
		totalErrs = append(totalErrs, testErrs...)
	}
	if cleanErrs != nil {
		totalErrs = append(totalErrs, cleanErrs...)
	}
	if len(totalErrs) != 0 {
		report.LogErrorsFatally(totalErrs)
	}
}
