package e2e

import (
	"context"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"sync/atomic"
	"syscall"
	"testing"

	"github.com/elliotchance/pie/v2"
	"github.com/tidbcloud/dataflow-service-e2e/internal/case_driver"
	. "github.com/tidbcloud/dataflow-service-e2e/internal/config"
	"github.com/tidbcloud/dataflow-service-e2e/internal/log"
)

var env string
var domain string
var apiMode string
var configFile string
var executor string
var cases string
var caseGroup string
var asyncDeps bool
var caseDriver case_driver.CaseDriver

// interruptCtx is cancelled on the first Ctrl-C to begin a graceful shutdown
// (cases stop and cleanup still runs). The second Ctrl-C force-exits the
// process immediately.
var (
	interruptCtx, interruptCancel = context.WithCancel(context.Background())
	interruptCount                int32
)

// installInterruptHandler listens for Ctrl-C (SIGINT). The first press triggers
// a graceful shutdown by canceling interruptCtx; running cases abort and the
// suite teardown (cleanup) still runs. A second press force-exits immediately.
func installInterruptHandler() {
	ch := make(chan os.Signal, 2)
	signal.Notify(ch, os.Interrupt, syscall.SIGINT)
	go func() {
		for range ch {
			if atomic.AddInt32(&interruptCount, 1) == 1 {
				log.NewDriverLogger("signal").Warn("Received Ctrl-C (interrupt #1): gracefully shutting down. Cases will stop and cleanup will run. Press Ctrl-C again to force-exit immediately.")
				interruptCancel()
				continue
			}
			log.NewDriverLogger("signal").Warn("Received Ctrl-C (interrupt #2): forcing immediate exit. Cleanup may be left incomplete. Goodbye!")
			// Give the message a moment to flush before the hard exit.
			os.Exit(1)
		}
	}()
}

func parseArgsWithDefaults() {
	flag.StringVar(&executor, "executor", "", "the executor of test")
	flag.StringVar(&env, "env", "", "the environment of test")
	flag.StringVar(&domain, "domain", DomainCDC, fmt.Sprintf("the domain of test cases: %v", AllDomains))
	flag.StringVar(&apiMode, "api-mode", "", fmt.Sprintf("the api mode: %v", AllApiModes))
	flag.StringVar(&configFile, "config-file", "", "env config file path override, default is ./config.{env}.yaml")
	flag.StringVar(&cases, "cases", "", "comma-separated case names to run, combines with -case-group, overrides cases2Run in config")
	flag.StringVar(&caseGroup, "case-group", "", "comma-separated case group names to run, combines with -cases, overrides caseGroup2Run in config")
	flag.BoolVar(&asyncDeps, "async-deps", true, "use per-case async dependency provisioning instead of barrier-sync")
	flag.Parse()
	if env == "" {
		env = "local"
	}
	fmt.Println(fmt.Sprintf("Run cases in %s environment", env))

	if !pie.Contains[string]([]string{"local", "dev", "staging", "prod"}, env) {
		panic(fmt.Sprintf("The env '%s' is not legal", env))
	}
}

func runTestSuite(t *testing.T, suiteName string, suitePlan SuitePlan, registerCases func()) {
	logger := log.NewDriverLogger(suiteName)
	defer func() {
		if r := recover(); r != nil {
			logger.Info("Recovered from panic: %v", r)
			logger.Sync()
			// make test fail if panic
			os.Exit(1)
		}
		logger.Sync()
	}()

	// init
	InitConfig(env, domain, apiMode, configFile, executor, cases, caseGroup, suitePlan)

	// setup
	registerCases()
	caseDriver = case_driver.NewCaseDriver(logger, interruptCtx, GlobalConfig.Cases2Run, GlobalConfig.CaseGroup2Run, asyncDeps)

	t.Run(suiteName, func(t *testing.T) {
		caseDriver.SetUp()
		// teardown
		t.Cleanup(func() {
			if GlobalConfig.MustCleanup != nil && *GlobalConfig.MustCleanup && caseDriver != nil {
				logger.Info("Teardown start...")
				caseDriver.TearDown()
				logger.Info("Teardown completed")
			}
		})

		// run tests
		caseDriver.Run(t)
	})
}

func TestMain(m *testing.M) {
	parseArgsWithDefaults()
	installInterruptHandler()
	os.Exit(m.Run())
}
