package engine_test

import (
	"context"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"strconv"
	"testing"
	"time"

	"github.com/gabehf/koito/engine"
	"github.com/gabehf/koito/internal/cfg"
	"github.com/gabehf/koito/internal/db/psql"
	"github.com/gabehf/koito/internal/utils"
	"github.com/ory/dockertest/v3"
)

var store *psql.Psql

func getTestGetenv(resource *dockertest.Resource) func(string) string {
	dir, err := utils.GenerateRandomString(8)
	if err != nil {
		panic(err)
	}

	listener, err := net.Listen("tcp", ":0")
	if err != nil {
		panic(fmt.Errorf("failed to get an open port: %w", err))
	}
	defer listener.Close()

	port := strconv.Itoa(listener.Addr().(*net.TCPAddr).Port)

	return func(env string) string {
		switch env {
		case cfg.ENABLE_STRUCTURED_LOGGING_ENV:
			return "true"
		case cfg.LOG_LEVEL_ENV:
			return "debug"
		case cfg.DATABASE_URL_ENV:
			return fmt.Sprintf("postgres://postgres:secret@localhost:%s", resource.GetPort("5432/tcp"))
		case cfg.DEFAULT_PASSWORD_ENV:
			return "testuser123"
		case cfg.DEFAULT_USERNAME_ENV:
			return "test"
		case cfg.CONFIG_DIR_ENV:
			return dir
		case cfg.LISTEN_PORT_ENV:
			return port
		case cfg.ALLOWED_HOSTS_ENV:
			return "*"
		case cfg.DISABLE_DEEZER_ENV, cfg.DISABLE_COVER_ART_ARCHIVE_ENV, cfg.DISABLE_MUSICBRAINZ_ENV, cfg.SKIP_IMPORT_ENV:
			return "true"
		default:
			return ""
		}
	}
}

func TestMain(m *testing.M) {
	// uses a sensible default on windows (tcp/http) and linux/osx (socket)
	pool, err := dockertest.NewPool("")
	if err != nil {
		log.Fatalf("Could not construct pool: %s", err)
	}

	// uses pool to try to connect to Docker
	err = pool.Client.Ping()
	if err != nil {
		log.Fatalf("Could not connect to Docker: %s", err)
	}

	// pulls an image, creates a container based on it and runs it
	resource, err := pool.Run("postgres", "latest", []string{"POSTGRES_PASSWORD=secret"})
	if err != nil {
		log.Fatalf("Could not start resource: %s", err)
	}

	getenv := getTestGetenv(resource)
	err = cfg.Load(getenv, "test")
	if err != nil {
		log.Fatalf("Could not load cfg: %s", err)
	}

	// exponential backoff-retry, because the application in the container might not be ready to accept connections yet
	if err := pool.Retry(func() error {
		var err error
		store, err = psql.New()
		if err != nil {
			log.Println("Failed to connect to test database, retrying...")
			return err
		}
		return store.Ping(context.Background())
	}); err != nil {
		log.Fatalf("Could not connect to database: %s", err)
	}

	go engine.Run(getenv, os.Stdout, "vTest")

	// Wait until the web server is reachable
	for i := 0; i < 20; i++ {
		url := fmt.Sprintf("http://%s/apis/web/v1/health", cfg.ListenAddr())
		client := &http.Client{
			Timeout: 2 * time.Second, // Set your desired timeout
		}
		resp, err := client.Get(url)
		if err != nil {
			if i >= 19 {
				log.Fatalf("Web server is not reachable: %s", err)
			}
			log.Printf("Failed to connect to web server at %s, retrying... (%d/20)", url, i+1)
			time.Sleep(1 * time.Second)
			continue
		}
		defer resp.Body.Close()
		if resp.StatusCode == http.StatusOK {
			err = nil
			break
		}
		log.Printf("Unexpected status code at %s, retrying... (%d/20)", url, i+1)
		time.Sleep(1 * time.Second)
	}

	code := m.Run()

	// You can't defer this because os.Exit doesn't care for defer
	if err := pool.Purge(resource); err != nil {
		log.Fatalf("Could not purge resource: %s", err)
	}

	err = os.RemoveAll(cfg.ConfigDir())
	if err != nil {
		log.Fatalf("Could not remove temporary config dir: %v", err)
	}

	os.Exit(code)
}

func host() string {
	return fmt.Sprintf("http://%s", cfg.ListenAddr())
}
