package casbin

import (
	"os"
	"time"

	mongodbadapter "github.com/casbin/mongodb-adapter/v4"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"go.uber.org/zap"
)

type AdapterConfig struct {
	DatabaseName      *string `mapstructure:"database_name"`
	DatabaseNameEnv   *string `mapstructure:"database_name_env"`
	CollectionName    *string `mapstructure:"collection_name"`
	CollectionNameEnv *string `mapstructure:"collection_name_env"`
	TimeoutSeconds    *int    `mapstructure:"timeout_seconds"`
	TimeoutSecondsEnv *string `mapstructure:"timeout_seconds_env"`
	IsFiltered        *bool   `mapstructure:"is_filtered"`
	IsFilteredEnv     *string `mapstructure:"is_filtered_env"`
}

type Config struct {
	ModelPath    *string        `mapstructure:"model_path"`
	ModelPathEnv *string        `mapstructure:"model_path_env"`
	Adapter      *AdapterConfig `mapstructure:"adapter"`
}

type EnforcerConfig struct {
	ModelPath string
	Adapter   *mongodbadapter.AdapterConfig
}

func getEnvValue(logger mlogger.Logger, varName, envVarName string, value, envValue *string) string {
	if value != nil && envValue != nil {
		logger.Warn("Both variable and environment variable are set, using environment variable value",
			zap.String("variable", varName), zap.String("environment_variable", envVarName), zap.String("value", *value), zap.String("env_value", os.Getenv(*envValue)))
	}

	if envValue != nil {
		return os.Getenv(*envValue)
	}

	if value != nil {
		return *value
	}

	return ""
}

func getEnvIntValue(logger mlogger.Logger, varName, envVarName string, value *int, envValue *string) int {
	if value != nil && envValue != nil {
		logger.Warn("Both variable and environment variable are set, using environment variable value",
			zap.String("variable", varName), zap.String("environment_variable", envVarName), zap.Int("value", *value), zap.String("env_value", os.Getenv(*envValue)))
	}

	if envValue != nil {
		envStr := os.Getenv(*envValue)
		if envStr != "" {
			if parsed, err := time.ParseDuration(envStr + "s"); err == nil {
				return int(parsed.Seconds())
			}
			logger.Warn("Invalid environment variable value for timeout", zap.String("environment_variable", envVarName), zap.String("value", envStr))
		}
	}

	if value != nil {
		return *value
	}

	return 30 // Default timeout in seconds
}

func getEnvBoolValue(logger mlogger.Logger, varName, envVarName string, value *bool, envValue *string) bool {
	if value != nil && envValue != nil {
		logger.Warn("Both variable and environment variable are set, using environment variable value",
			zap.String("variable", varName), zap.String("environment_variable", envVarName), zap.Bool("value", *value), zap.String("env_value", os.Getenv(*envValue)))
	}

	if envValue != nil {
		envStr := os.Getenv(*envValue)
		switch envStr {
		case "true", "1":
			return true
		case "false", "0":
			return false
		}
		logger.Warn("Invalid environment variable value for boolean", zap.String("environment_variable", envVarName), zap.String("value", envStr))
	}

	if value != nil {
		return *value
	}

	return false // Default for boolean
}

func PrepareConfig(logger mlogger.Logger, config *Config) (*EnforcerConfig, error) {
	if config == nil {
		return nil, merrors.Internal("No configuration provided")
	}

	adapter := &mongodbadapter.AdapterConfig{
		DatabaseName:   getEnvValue(logger, "database_name", "database_name_env", config.Adapter.DatabaseName, config.Adapter.DatabaseNameEnv),
		CollectionName: getEnvValue(logger, "collection_name", "collection_name_env", config.Adapter.CollectionName, config.Adapter.CollectionNameEnv),
		Timeout:        time.Duration(getEnvIntValue(logger, "timeout_seconds", "timeout_seconds_env", config.Adapter.TimeoutSeconds, config.Adapter.TimeoutSecondsEnv)) * time.Second,
		IsFiltered:     getEnvBoolValue(logger, "is_filtered", "is_filtered_env", config.Adapter.IsFiltered, config.Adapter.IsFilteredEnv),
	}

	if len(adapter.DatabaseName) == 0 {
		logger.Error("Database name is not set")
		return nil, merrors.InvalidArgument("database name must be provided", "adapter.databaseName")
	}

	path := getEnvValue(logger, "model_path", "model_path_env", config.ModelPath, config.ModelPathEnv)

	logger.Info("Configuration prepared",
		zap.String("model_path", path),
		zap.String("database_name", adapter.DatabaseName),
		zap.String("collection_name", adapter.CollectionName),
		zap.Duration("timeout", adapter.Timeout),
		zap.Bool("is_filtered", adapter.IsFiltered),
	)

	return &EnforcerConfig{ModelPath: path, Adapter: adapter}, nil
}
