package pilgrim_conf

import (
	"log"

	"mvvasilev.dev/pilgrim/internal/pilgrim_common"
)

const DefaultMigrationDirectory = "./migrations"
const DefaultMigrationTable = "pilgrim_migrations"

type MigrationPhase bool

const (
	MigrationPhase_Up   MigrationPhase = true
	MigrationPhase_Down                = false
)

type PilgrimConfigurationRetriever interface {
	UrlParts() UrlParts
	MigrationDirectory() string
	MigrationPhase() MigrationPhase
	MigrationScript() string
	MigrationStrictOrdering() bool
	MigrationIsChecksumValidationEnabled() bool
	MigrationTable() string
	MigrationSchema() string

	ValidateChecksums() bool
	ValidateLatest() bool
	ValidateMigrationOrder() bool
}

type pilgrimContext struct {
	urlParts                             UrlParts
	migrationDirectory                   string
	migrationPhase                       MigrationPhase
	migrationScript                      string
	migrationStrictOrdering              bool
	migrationIsChecksumValidationEnabled bool
	migrationSchema                      string
	migrationTable                       string

	validateMigrationOrder bool
	validateLatest         bool
	validateChecksums      bool
}

func NewPilgrimContext(cli CliFlagRetriever, env EnvVarRetriever) *pilgrimContext {

	cliUrl, _ := cli.Url()
	cliUrlParts := ParseDatabaseUrl(cliUrl)
	envUrl, _ := env.Url()
	envUrlParts := ParseDatabaseUrl(envUrl)

	urlParts := UrlParts{
		Driver: PickFirstAvailable(
			cli.Driver,
			func() (DbDriver, bool) { return DbDriver(cliUrlParts.Driver), cliUrlParts.Driver != EmptyString },
			env.Driver,
			func() (DbDriver, bool) { return DbDriver(envUrlParts.Driver), envUrlParts.Driver != EmptyString },
		),
		Username: PickFirstAvailable(
			cli.Username,
			func() (string, bool) { return cliUrlParts.Username, cliUrlParts.Username != EmptyString },
			env.Username,
			func() (string, bool) { return envUrlParts.Username, envUrlParts.Username != EmptyString },
		),
		Password: PickFirstAvailable(
			cli.Password,
			func() (string, bool) { return cliUrlParts.Password, cliUrlParts.Password != EmptyString },
			env.Password,
			func() (string, bool) { return envUrlParts.Password, envUrlParts.Password != EmptyString },
		),
		Host: PickFirstAvailable(
			cli.Host,
			func() (string, bool) { return cliUrlParts.Host, cliUrlParts.Host != EmptyString },
			env.Host,
			func() (string, bool) { return envUrlParts.Host, envUrlParts.Host != EmptyString },
		),
		Port: PickFirstAvailable(
			cli.Port,
			func() (string, bool) { return cliUrlParts.Port, cliUrlParts.Port != EmptyString },
			env.Port,
			func() (string, bool) { return envUrlParts.Port, envUrlParts.Port != EmptyString },
		),
		Segments: PickFirstAvailable(
			cli.Segments,
			func() ([]string, bool) { return cliUrlParts.Segments, cliUrlParts.Segments != nil },
			env.Segments,
			func() ([]string, bool) { return envUrlParts.Segments, envUrlParts.Segments != nil },
		),
		Arguments: PickFirstAvailable(
			cli.Args,
			func() (map[string]string, bool) { return cliUrlParts.Arguments, cliUrlParts.Arguments != nil },
			env.Args,
			func() (map[string]string, bool) { return envUrlParts.Arguments, envUrlParts.Arguments != nil },
		),
	}

	isValid, err := urlParts.Validate()

	if !isValid {
		log.Fatalln(err)
	}

	migrationPhase, migrationScript, _ := determineMigrationPhaseAndScript(cli)

	migrationDirectory := PickFirstAvailable(cli.Directory, env.Directory, func() (string, bool) { return DefaultMigrationDirectory, true })
	migrationSchema := PickFirstAvailable(cli.MigrationTableSchema, env.MigrationTableSchema, pickFirstDefault(EmptyString)) // Default depends on driver, set empty for now
	migrationTable := PickFirstAvailable(cli.MigrationTable, env.MigrationTable, pickFirstDefault(DefaultMigrationTable))

	migrationStrictOrdering, _ := cli.IsStrictOrderingEnabled()
	migrationIsChecksumValidationEnabled, _ := cli.IsChecksumValidationEnabled()

	validateMigrationOrder, _ := cli.IsValidatingMigrationOrder()
	validateLatest, _ := cli.IsValidatingLatest()
	validateChecksums, _ := cli.IsValidatingLatest()

	return &pilgrimContext{
		urlParts,
		migrationDirectory,
		migrationPhase,
		migrationScript,
		migrationStrictOrdering,
		migrationIsChecksumValidationEnabled,
		migrationSchema,
		migrationTable,
		validateMigrationOrder,
		validateLatest,
		validateChecksums,
	}
}

type MigrationPhaseRetriever interface {
	Script() (script string, isScriptProvided bool)
	IsUp() (isUp, isUpProvided bool)
	IsDown() (isDown, isDownProvided bool)
}

func determineMigrationPhaseAndScript(cli MigrationPhaseRetriever) (phase MigrationPhase, script string, err error) {
	script, _ = cli.Script()

	isUp, isUpProvided := cli.IsUp()
	_, isDownProvided := cli.IsDown()

	if (!isUpProvided && !isDownProvided) || (isUpProvided && isDownProvided) {
		return MigrationPhase_Down, EmptyString, pilgrim_common.PilgrimInvalidError("Must provide either --up or --down, but not both.")
	}

	if isUp {
		phase = MigrationPhase_Up
	} else {
		phase = MigrationPhase_Down
	}

	return
}

func pickFirstDefault[T any](defaultVal T) func() (value T, provided bool) {
	return func() (value T, provided bool) {
		return defaultVal, true
	}
}

func PickFirstAvailable[T any](valueProviders ...func() (value T, provided bool)) (value T) {
	for _, getValue := range valueProviders {
		val, provided := getValue()

		if provided {
			return val
		}
	}

	value, _ = valueProviders[len(valueProviders)-1]()
	return
}

func (c *pilgrimContext) UrlParts() UrlParts {
	return c.urlParts
}

func (c *pilgrimContext) MigrationDirectory() string {
	return c.migrationDirectory
}

func (c *pilgrimContext) MigrationPhase() MigrationPhase {
	return MigrationPhase(c.migrationPhase)
}

func (c *pilgrimContext) MigrationScript() string {
	return c.migrationScript
}

func (c *pilgrimContext) MigrationStrictOrdering() bool {
	return c.migrationStrictOrdering
}

func (c *pilgrimContext) MigrationIsChecksumValidationEnabled() bool {
	return c.migrationIsChecksumValidationEnabled
}

func (c *pilgrimContext) MigrationSchema() string {
	return c.migrationSchema
}

func (c *pilgrimContext) MigrationTable() string {
	return c.migrationTable
}

func (c *pilgrimContext) ValidateChecksums() bool {
	return c.validateChecksums
}

func (c *pilgrimContext) ValidateMigrationOrder() bool {
	return c.validateMigrationOrder
}

func (c *pilgrimContext) ValidateLatest() bool {
	return c.validateLatest
}