package main

import (
	"fmt"
	"io"
	"os"
	"reflect"
	"strconv"
	"strings"

	"github.com/BurntSushi/toml"
)

type ComposerConfigFile struct {
	Koji          KojiAPIConfig     `toml:"koji"`
	Worker        WorkerAPIConfig   `toml:"worker"`
	WeldrAPI      WeldrAPIConfig    `toml:"weldr_api"`
	DistroAliases map[string]string `toml:"distro_aliases" env:"DISTRO_ALIASES"`
	LogLevel      string            `toml:"log_level"`
	LogFormat     string            `toml:"log_format"`
	DNFJson       string            `toml:"dnf-json"`
	SplunkHost    string            `env:"SPLUNK_HEC_HOST"`
	SplunkPort    string            `env:"SPLUNK_HEC_PORT"`
	SplunkToken   string            `env:"SPLUNK_HEC_TOKEN"`
	GlitchTipDSN  string            `env:"GLITCHTIP_DSN"`
}

type KojiAPIConfig struct {
	AllowedDomains          []string `toml:"allowed_domains"`
	CA                      string   `toml:"ca"`
	EnableTLS               bool     `toml:"enable_tls"`
	EnableMTLS              bool     `toml:"enable_mtls"`
	EnableJWT               bool     `toml:"enable_jwt"`
	JWTKeysURLs             []string `toml:"jwt_keys_urls"`
	JWTKeysCA               string   `toml:"jwt_ca_file"`
	JWTACLFile              string   `toml:"jwt_acl_file"`
	JWTTenantProviderFields []string `toml:"jwt_tenant_provider_fields"`
}

type WorkerAPIConfig struct {
	AllowedDomains          []string `toml:"allowed_domains"`
	CA                      string   `toml:"ca"`
	RequestJobTimeout       string   `toml:"request_job_timeout"`
	BasePath                string   `toml:"base_path"`
	EnableArtifacts         bool     `toml:"enable_artifacts"`
	PGHost                  string   `toml:"pg_host" env:"PGHOST"`
	PGPort                  string   `toml:"pg_port" env:"PGPORT"`
	PGDatabase              string   `toml:"pg_database" env:"PGDATABASE"`
	PGUser                  string   `toml:"pg_user" env:"PGUSER"`
	PGPassword              string   `toml:"pg_password" env:"PGPASSWORD"`
	PGSSLMode               string   `toml:"pg_ssl_mode" env:"PGSSLMODE"`
	PGMaxConns              int      `toml:"pg_max_conns" env:"PGMAXCONNS"`
	EnableTLS               bool     `toml:"enable_tls"`
	EnableMTLS              bool     `toml:"enable_mtls"`
	EnableJWT               bool     `toml:"enable_jwt"`
	JWTKeysURLs             []string `toml:"jwt_keys_urls"`
	JWTKeysCA               string   `toml:"jwt_ca_file"`
	JWTACLFile              string   `toml:"jwt_acl_file"`
	JWTTenantProviderFields []string `toml:"jwt_tenant_provider_fields"`
}

type WeldrAPIConfig struct {
	DistroConfigs map[string]WeldrDistroConfig `toml:"distros"`
}

type WeldrDistroConfig struct {
	ImageTypeDenyList []string `toml:"image_type_denylist"`
}

// weldrDistrosImageTypeDenyList returns a map of distro-specific Image Type
// deny lists for Weldr API.
func (c *ComposerConfigFile) weldrDistrosImageTypeDenyList() map[string][]string {
	distrosImageTypeDenyList := map[string][]string{}

	for distro, distroConfig := range c.WeldrAPI.DistroConfigs {
		if distroConfig.ImageTypeDenyList != nil {
			distrosImageTypeDenyList[distro] = append([]string{}, distroConfig.ImageTypeDenyList...)
		}
	}

	return distrosImageTypeDenyList
}

// GetDefaultConfig returns the default configuration of osbuild-composer
// Defaults:
//   - 'azure-rhui', 'azure-sap-rhui', 'ec2', 'ec2-ha', 'ec2-sap' image types on 'rhel-*'
//     are not exposed via Weldr API
func GetDefaultConfig() *ComposerConfigFile {
	return &ComposerConfigFile{
		Koji: KojiAPIConfig{
			EnableTLS:  true,
			EnableMTLS: true,
			EnableJWT:  false,
		},
		Worker: WorkerAPIConfig{
			RequestJobTimeout: "0",
			BasePath:          "/api/worker/v1",
			EnableArtifacts:   true,
			EnableTLS:         true,
			EnableMTLS:        true,
			EnableJWT:         false,
		},
		WeldrAPI: WeldrAPIConfig{
			map[string]WeldrDistroConfig{
				"rhel-*": {
					ImageTypeDenyList: []string{
						"azure-eap7-rhui",
						"azure-rhui",
						"azure-sap-rhui",
						"ec2",
						"ec2-ha",
						"ec2-sap",
						"gce-rhui",
					},
				},
				"fedora-*": {
					ImageTypeDenyList: []string{
						"iot-bootable-container",
					},
				},
			},
		},
		DistroAliases: map[string]string{
			"rhel-7": "rhel-7.9",
			"rhel-8": "rhel-8.10",
			"rhel-9": "rhel-9.4",
		},
		LogLevel:  "info",
		LogFormat: "text",
		DNFJson:   "/usr/libexec/osbuild-depsolve-dnf",
	}
}

func LoadConfig(name string) (*ComposerConfigFile, error) {
	c := GetDefaultConfig()
	_, err := toml.DecodeFile(name, c)
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}
	err = loadConfigFromEnv(c)
	if err != nil {
		return nil, err
	}
	return c, nil
}

// envStrToMap converts map string to map[string]string
func envStrToMap(s string) (map[string]string, error) {
	result := map[string]string{}
	if s == "" {
		return result, nil
	}

	parts := strings.Split(s, ",")
	for _, part := range parts {
		keyValue := strings.Split(part, "=")
		if len(keyValue) != 2 {
			return nil, fmt.Errorf("Invalid key-value pair in map string: %s", part)
		}
		result[keyValue[0]] = keyValue[1]
	}
	return result, nil
}

func loadConfigFromEnv(intf interface{}) error {
	t := reflect.TypeOf(intf).Elem()
	v := reflect.ValueOf(intf).Elem()

	for i := 0; i < v.NumField(); i++ {
		fieldT := t.Field(i)
		fieldV := v.Field(i)
		kind := fieldV.Kind()

		switch kind {
		case reflect.String:
			key, ok := fieldT.Tag.Lookup("env")
			if !ok {
				continue
			}
			confV, ok := os.LookupEnv(key)
			if !ok {
				continue
			}
			fieldV.SetString(confV)
		case reflect.Int:
			key, ok := fieldT.Tag.Lookup("env")
			if !ok {
				continue
			}
			confV, ok := os.LookupEnv(key)
			if !ok {
				continue
			}
			value, err := strconv.ParseInt(confV, 10, 64)
			if err != nil {
				return err
			}
			fieldV.SetInt(value)
		case reflect.Bool:
			// no-op
			continue
		case reflect.Slice:
			// no-op
			continue
		case reflect.Map:
			key, ok := fieldT.Tag.Lookup("env")
			if !ok {
				continue
			}
			// handle only map[string]string
			if fieldV.Type().Key().Kind() != reflect.String || fieldV.Type().Elem().Kind() != reflect.String {
				return fmt.Errorf("Unsupported map type for loading from ENV: %s", kind)
			}
			confV, ok := os.LookupEnv(key)
			if !ok {
				continue
			}
			value, err := envStrToMap(confV)
			if err != nil {
				return err
			}
			fieldV.Set(reflect.ValueOf(value))
		case reflect.Struct:
			err := loadConfigFromEnv(fieldV.Addr().Interface())
			if err != nil {
				return err
			}
		default:
			return fmt.Errorf("Unsupported type: %s", kind)
		}
	}
	return nil
}

func DumpConfig(c ComposerConfigFile, w io.Writer) error {
	// sensor sensitive fields
	c.Worker.PGPassword = ""
	return toml.NewEncoder(w).Encode(c)
}
