// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package test

import (
	"net"
	"os"
	"os/exec"
	"time"
)

const (
	testRedisHost    = "127.0.0.1"
	testRedisPort    = "6379"
	testRedisAddr    = testRedisHost + ":" + testRedisPort
	testRedisConnStr = "redis://" + testRedisAddr + "/0"
)

// waitRedisReady reports whether redis accepts connections within dur. Redis
// binds its listener last during startup, so a successful dial means it can
// serve. A plain dial, not a redis PING: the client retries its pool on a
// refused connect, which makes the "is one already running" probe take ~1s.
func waitRedisReady(dur time.Duration) bool {
	for start := time.Now(); ; time.Sleep(50 * time.Millisecond) {
		conn, err := net.DialTimeout("tcp", testRedisAddr, time.Second)
		if err == nil {
			_ = conn.Close()
			return true
		}
		if time.Since(start) > dur {
			return false
		}
	}
}

func redisServerCmd(t TestingT) *exec.Cmd {
	redisServerProg, err := exec.LookPath("redis-server")
	if err != nil {
		return nil
	}
	return &exec.Cmd{
		Path:   redisServerProg,
		Args:   []string{redisServerProg, "--bind", testRedisHost, "--port", testRedisPort},
		Dir:    t.TempDir(),
		Stdin:  os.Stdin,
		Stdout: os.Stdout,
		Stderr: os.Stderr,
	}
}

// PrepareTestRedis returns a connection string to a running redis, starting one
// for the duration of the test if the port is free.
func PrepareTestRedis(t TestingT) string {
	if waitRedisReady(0) {
		return testRedisConnStr
	}
	redisServer := redisServerCmd(t)
	if redisServer == nil {
		if AllowSkipExternalService() {
			t.Skipf("redis-server command not found, skipped")
		} else {
			t.Fatalf("no redis server or command, but skipping is not allowed")
		}
		return testRedisConnStr
	}
	if err := redisServer.Start(); err != nil {
		t.Fatalf("failed to start redis-server: %v", err)
	}
	t.Cleanup(func() {
		_ = redisServer.Process.Signal(os.Interrupt)
		_ = redisServer.Wait()
	})
	if !waitRedisReady(5 * time.Second) {
		t.Fatalf("failed to start redis-server")
	}
	return testRedisConnStr
}
