package testdb

import (
	"database/sql"
	"fmt"
	"log"
	"strconv"

	"github.com/ory/dockertest/v3"
	"github.com/ory/dockertest/v3/docker"
	_ "github.com/tursodatabase/libsql-client-go/libsql"
)

const (
	// ghcr.io/tursodatabase/libsql-server:v0.23.7
	TURSO_IMAGE   = "ghcr.io/tursodatabase/libsql-server"
	TURSO_VERSION = "v0.24.7"
	TURSO_PORT    = "8080"
)

// NewTurso starts a Turso docker container. Returns db connection and a docker cleanup function.
func NewTurso(options ...OptionsFunc) (db *sql.DB, cleanup func(), err error) {
	return newTurso(options...)
}

func newTurso(opts ...OptionsFunc) (*sql.DB, func(), error) {
	option := &options{}
	for _, f := range opts {
		f(option)
	}
	// Uses a sensible default on windows (tcp/http) and linux/osx (socket).
	pool, err := dockertest.NewPool("")
	if err != nil {
		return nil, nil, err
	}
	runOptions := &dockertest.RunOptions{
		Repository:   TURSO_IMAGE,
		Tag:          TURSO_VERSION,
		Labels:       map[string]string{"goose_test": "1"},
		PortBindings: make(map[docker.Port][]docker.PortBinding),
	}
	if option.debug {
		runOptions.Env = append(runOptions.Env, "RUST=trace")
	} else {
		runOptions.Env = append(runOptions.Env, "RUST=error")
	}
	if option.bindPort > 0 {
		runOptions.PortBindings[TURSO_PORT+"/tcp"] = []docker.PortBinding{
			{HostPort: strconv.Itoa(option.bindPort)},
		}
	}
	container, err := pool.RunWithOptions(
		runOptions,
		func(config *docker.HostConfig) {
			// Set AutoRemove to true so that stopped container goes away by itself.
			config.AutoRemove = true
			config.RestartPolicy = docker.RestartPolicy{Name: "no"}
		},
	)
	if err != nil {
		return nil, nil, err
	}
	cleanup := func() {
		if option.debug {
			// User must manually delete the Docker container.
			return
		}
		if err := pool.Purge(container); err != nil {
			log.Printf("failed to purge resource: %v", err)
		}
	}
	// Fetch port assigned to container

	var db *sql.DB
	// Exponential backoff-retry, because the application in the container
	// might not be ready to accept connections yet.
	if err := pool.Retry(func() error {
		db, err = tursoOpenDB(container)
		return err
	}); err != nil {
		return nil, cleanup, fmt.Errorf("could not connect to docker database: %w", err)
	}
	return db, cleanup, nil
}

func tursoOpenDB(container *dockertest.Resource) (*sql.DB, error) {
	address := fmt.Sprintf("http://127.0.0.1:%s", container.GetPort(TURSO_PORT+"/tcp"))
	db, err := sql.Open("libsql", address)
	if err != nil {
		return db, err
	}
	// let's do a ping to be sure we are connected
	var result int
	err = db.QueryRow("SELECT 1").Scan(&result)
	if err != nil {
		return nil, err
	}
	return db, nil
}