package config

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import (
	"bytes"
	"errors"
	"fmt"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/apache/trafficcontrol/v8/cache-config/t3cutil"
	"github.com/apache/trafficcontrol/v8/lib/go-log"

	"github.com/pborman/getopt/v2"
)

const AppName = "t3c-apply"

var TSHome string = "/opt/trafficserver"

const DefaultTSConfigDir = "/opt/trafficserver/etc/trafficserver"

const (
	StatusDir          = "/var/lib/trafficcontrol-cache-config/status"
	Chkconfig          = "/sbin/chkconfig"
	Service            = "/sbin/service"
	SystemCtl          = "/bin/systemctl"
	TmpBase            = "/tmp/trafficcontrol-cache-config"
	TrafficCtl         = "/bin/traffic_ctl"
	TrafficServerOwner = "ats"
)

type SvcManagement int

const (
	Unknown SvcManagement = 0
	SystemD SvcManagement = 1
	SystemV SvcManagement = 2 // legacy System V Init.
)

func (s SvcManagement) String() string {
	switch s {
	case Unknown:
		return "Unknown"
	case SystemD:
		return "SystemD"
	case SystemV:
		return "SystemV"
	}
	return "Unknown"
}

type Cfg struct {
	LogLocationDebug    string
	LogLocationErr      string
	LogLocationInfo     string
	LogLocationWarn     string
	CacheHostName       string
	SvcManagement       SvcManagement
	Retries             int
	ReverseProxyDisable bool
	RpmDBOk             bool
	SkipOSCheck         bool
	UseStrategies       t3cutil.UseStrategiesFlag
	TOInsecure          bool
	TOTimeoutMS         time.Duration
	TOUser              string
	TOPass              string
	TOURL               string
	DNSLocalBind        bool
	WaitForParents      bool
	YumOptions          string
	// UseGit is whether to create and maintain a git repo of config changes.
	// Note this only applies to the ATS config directory inferred or set via the flag.
	//      It does not do anything for config files generated outside that location.
	UseGit                      UseGitFlag
	NoCache                     bool
	SyncDSUpdatesIPAllow        bool
	OmitViaStringRelease        bool
	NoOutgoingIP                bool
	DisableParentConfigComments bool
	DefaultClientEnableH2       *bool
	DefaultClientTLSVersions    *string
	// MaxMindLocation is a URL string for a download location for a maxmind database
	// for use with either HeaderRewrite or Maxmind_ACL plugins
	MaxMindLocation string
	TsHome          string
	TsConfigDir     string

	ServiceAction          t3cutil.ApplyServiceActionFlag
	NoConfirmServiceAction bool

	ReportOnly        bool
	GoDirect          string
	Files             t3cutil.ApplyFilesFlag
	InstallPackages   bool
	IgnoreUpdateFlag  bool
	NoUnsetUpdateFlag bool
	UpdateIPAllow     bool
	Version           string
	GitRevision       string
	LocalATSVersion   string
	CacheType         string
}

func (cfg Cfg) AppVersion() string { return t3cutil.VersionStr(AppName, cfg.Version, cfg.GitRevision) }

type UseGitFlag string

const (
	UseGitAuto    = "auto"
	UseGitYes     = "yes"
	UseGitNo      = "no"
	UseGitInvalid = ""
)

func StrToUseGitFlag(str string) UseGitFlag {
	str = strings.ToLower(strings.TrimSpace(str))
	switch str {
	case UseGitAuto:
		fallthrough
	case UseGitYes:
		fallthrough
	case UseGitNo:
		return UseGitFlag(str)
	default:
		return UseGitInvalid
	}
}

type WaitForParentsFlag string

const WaitForParentsDefault = WaitForParentsReval
const TimeAndDateLayout = "Jan 2, 2006 15:04 MST"

const (
	WaitForParentsTrue    = "true"
	WaitForParentsFalse   = "false"
	WaitForParentsReval   = "reval"
	WaitForParentsInvalid = ""
)

func StrToWaitForParentsFlag(str string) WaitForParentsFlag {
	str = strings.ToLower(strings.TrimSpace(str))
	switch str {
	case WaitForParentsTrue:
		fallthrough
	case WaitForParentsFalse:
		fallthrough
	case WaitForParentsReval:
		return WaitForParentsFlag(str)
	default:
		return WaitForParentsInvalid
	}
}

func (cfg Cfg) ErrorLog() log.LogLocation   { return log.LogLocation(cfg.LogLocationErr) }
func (cfg Cfg) WarningLog() log.LogLocation { return log.LogLocation(cfg.LogLocationWarn) }
func (cfg Cfg) InfoLog() log.LogLocation    { return log.LogLocation(cfg.LogLocationInfo) }
func (cfg Cfg) DebugLog() log.LogLocation   { return log.LogLocation(cfg.LogLocationDebug) }
func (cfg Cfg) EventLog() log.LogLocation   { return log.LogLocation(log.LogLocationNull) } // event logging is not used.

func directoryExists(dir string) (bool, os.FileInfo) {
	info, err := os.Stat(dir)
	if os.IsNotExist(err) {
		return false, nil
	}
	return info.IsDir(), info
}

// derives the ATS Installation directory from
// the rpm config file list.
func GetTSPackageHome() string {
	var dir []string
	var output bytes.Buffer
	var tsHome string = ""
	var files []string

	cmd := exec.Command("/bin/rpm", "-q", "-c", "trafficserver")
	cmd.Stdout = &output
	err := cmd.Run()
	// on error or if the trafficserver rpm is not installed indicated
	// by a return code of '1', return an empty string.
	if err != nil || cmd.ProcessState.ExitCode() == 1 {
		return ""
	} else {
		responseStr := string(output.Bytes())
		files = strings.Split(responseStr, "\n")
	}

	if files != nil && err == nil { // trafficserver is installed, derive TSHome
		for ii := range files {
			line := strings.TrimSpace(files[ii])
			if strings.Contains(line, "etc/trafficserver") {
				dir = strings.Split(line, "/etc")
				break
			}
		}
		if dir != nil && len(dir) >= 0 {
			if ok, _ := directoryExists(dir[0]); ok == true {
				tsHome = dir[0]
			}
		}
	}
	return tsHome
}

const (
	rpmDBBdb             = "bdb"
	rpmDBSquLite         = "sqlite"
	rpmDBUnknown         = "unknown"
	rpmDBVerifyCmd       = "/usr/lib/rpm/rpmdb_verify"
	sqliteRpmDbVerifyCmd = "/bin/sqlite3"
	rpmDir               = "/var/lib/rpm"
	sqliteRpmDB          = "rpmdb.sqlite"
)

// getRpmDBBackend uses "%_db_backend" macro to get the database type
func getRpmDBBackend() (string, error) {
	var outBuf bytes.Buffer
	cmd := exec.Command("/bin/rpm", "-E", "%_db_backend")
	cmd.Stdout = &outBuf
	err := cmd.Run()
	if err != nil {
		return rpmDBUnknown, err
	}
	return strings.TrimSpace(outBuf.String()), nil
}

// isSqliteInstalled looks to see if the sqlite3 executable
// is installed which is needed to do the db verify
func isSqliteInstalled() bool {
	sqliteUtil := isCommandAvailable("/bin/sqlite3")
	return sqliteUtil
}

// verifies the rpm database files. if there is any database corruption
// it will return false
func verifyRpmDB(rpmDir string) bool {
	exclude := regexp.MustCompile(`(^\.|^__)`)
	dbFiles, err := os.ReadDir(rpmDir)
	if err != nil {
		return false
	}
	for _, file := range dbFiles {
		if exclude.Match([]byte(file.Name())) {
			continue
		}
		cmd := exec.Command(rpmDBVerifyCmd, rpmDir+"/"+file.Name())
		err := cmd.Run()
		if err != nil || cmd.ProcessState.ExitCode() > 0 {
			return false
		}
	}
	return true
}

// verifySqliteRpmDB runs PRAGMA quick_check
// requires /bin/sqlite3
func verifySqliteRpmDB(sqliteDB string) bool {
	args := []string{sqliteDB, `PRAGMA quick_check`}
	cmd := exec.Command(sqliteRpmDbVerifyCmd, args...)
	err := cmd.Run()
	if err != nil || cmd.ProcessState.ExitCode() > 0 {
		return false
	}
	return true
}

func GetCfg(appVersion string, gitRevision string) (Cfg, error) {
	var err error
	toInfoLog := []string{}

	version := getopt.BoolLong("version", 'E', "Print version information and exit.")
	cacheHostNamePtr := getopt.StringLong("cache-host-name", 'H', "", "Host name of the cache to generate config for. Must be the server host name in Traffic Ops, not a URL, and not the FQDN")
	retriesPtr := getopt.IntLong("num-retries", 'r', 3, "[number] retry connection to Traffic Ops URL [number] times, default is 3")
	reverseProxyDisablePtr := getopt.BoolLong("reverse-proxy-disable", 'p', "[false | true] bypass the reverse proxy even if one has been configured default is false")
	skipOSCheckPtr := getopt.BoolLong("skip-os-check", 'C', "[false | true] skip os check, default is false")
	toInsecurePtr := getopt.BoolLong("traffic-ops-insecure", 'I', "[true | false] ignore certificate errors from Traffic Ops")
	toTimeoutMSPtr := getopt.IntLong("traffic-ops-timeout-milliseconds", 't', 30000, "Timeout in milli-seconds for Traffic Ops requests, default is 30000")
	toURLPtr := getopt.StringLong("traffic-ops-url", 'u', "", "Traffic Ops URL. Must be the full URL, including the scheme. Required. May also be set with the environment variable TO_URL")
	toUserPtr := getopt.StringLong("traffic-ops-user", 'U', "", "Traffic Ops username. Required. May also be set with the environment variable TO_USER")
	toPassPtr := getopt.StringLong("traffic-ops-password", 'P', "", "Traffic Ops password. Required. May also be set with the environment variable TO_PASS")
	tsHomePtr := getopt.StringLong("trafficserver-home", 'R', "", "Trafficserver Package directory. May also be set with the environment variable TS_HOME")
	dnsLocalBindPtr := getopt.BoolLong("dns-local-bind", 'b', "[true | false] whether to use the server's Service Addresses to set the ATS DNS local bind address")
	help := getopt.BoolLong("help", 'h', "Print usage information and exit")
	useGitStr := getopt.StringLong("git", 'g', "auto", "Create and use a git repo in the config directory. Options are yes, no, and auto. If yes, create and use. If auto, use if it exist. Default is auto.")
	noCachePtr := getopt.BoolLong("no-cache", 'n', "Whether to not use a cache and make conditional requests to Traffic Ops")
	syncdsUpdatesIPAllowPtr := getopt.BoolLong("syncds-updates-ipallow", 'S', "Whether syncds mode will update ipallow. This exists because ATS had a bug where reloading after changing ipallow would block everything. Default is false.")
	omitViaStringReleasePtr := getopt.BoolLong("omit-via-string-release", 'e', "Whether to set the records.config via header to the ATS release from the RPM. Default true.")
	noOutgoingIP := getopt.BoolLong("no-outgoing-ip", 'i', "Whether to not set the records.config outgoing IP to the server's addresses in Traffic Ops. Default is false.")
	disableParentConfigCommentsPtr := getopt.BoolLong("disable-parent-config-comments", 'c', "Whether to disable verbose parent.config comments. Default false.")
	defaultEnableH2 := getopt.BoolLong("default-client-enable-h2", '2', "Whether to enable HTTP/2 on Delivery Services by default, if they have no explicit Parameter. This is irrelevant if ATS records.config is not serving H2. If omitted, H2 is disabled.")
	defaultClientTLSVersions := getopt.StringLong("default-client-tls-versions", 'V', "", "Comma-delimited list of default TLS versions for Delivery Services with no Parameter, e.g. --default-tls-versions='1.1,1.2,1.3'. If omitted, all versions are enabled.")
	maxmindLocationPtr := getopt.StringLong("maxmind-location", 'M', "", "URL of a maxmind gzipped database file, to be installed into the trafficserver etc directory.")
	verbosePtr := getopt.CounterLong("verbose", 'v', `Log verbosity. Logging is output to stderr. By default, errors are logged. To log warnings, pass '-v'. To log info, pass '-vv'. To omit error logging, see '-s'`)
	cache := getopt.StringLong("cache", 'T', "ats", "Cache server type. Generate configuration files for specific cache server type, e.g. 'ats', 'varnish'.")
	const silentFlagName = "silent"
	silentPtr := getopt.BoolLong(silentFlagName, 's', `Silent. Errors are not logged, and the 'verbose' flag is ignored. If a fatal error occurs, the return code will be non-zero but no text will be output to stderr`)

	const goDirectFlagName = "go-direct"
	goDirectPtr := getopt.StringLong(goDirectFlagName, 'G', "old", "[true|false|old] default will set go_direct to old and it will be based on opposite of parent_is_proxy directivefalse, you can also set go_direct true, or false.")

	const waitForParentsFlagName = "wait-for-parents"
	waitForParentsPtr := getopt.BoolLong(waitForParentsFlagName, 'W', "[true | false] do not update if parent_pending = 1 in the update json. Default is false")

	const serviceActionFlagName = "service-action"
	const defaultServiceAction = t3cutil.ApplyServiceActionFlagReload
	serviceActionPtr := getopt.EnumLong(serviceActionFlagName, 'a', []string{string(t3cutil.ApplyServiceActionFlagReload), string(t3cutil.ApplyServiceActionFlagRestart), string(t3cutil.ApplyServiceActionFlagNone), ""}, "", "action to perform on Traffic Server and other system services. Only reloads if necessary, but always restarts. Default is 'reload'")

	const noConfirmServiceActionFlagName = "no-confirm-service-action"
	noConfirmServiceAction := getopt.BoolLong(noConfirmServiceActionFlagName, 0, "Whether to skip waiting and confirming the service action succeeded (reload or restart) via t3c-tail. Default is false.")

	const reportOnlyFlagName = "report-only"
	reportOnlyPtr := getopt.BoolLong(reportOnlyFlagName, 'o', "Log information about necessary files and actions, but take no action. Default is false")

	const filesFlagName = "files"
	const defaultFiles = t3cutil.ApplyFilesFlagAll
	filesPtr := getopt.EnumLong(filesFlagName, 'f', []string{string(t3cutil.ApplyFilesFlagAll), string(t3cutil.ApplyFilesFlagReval), ""}, "", "[all | reval] Which files to generate. If reval, the Traffic Ops server reval_pending flag is used instead of the upd_pending flag. Default is 'all'")

	const installPackagesFlagName = "install-packages"
	installPackagesPtr := getopt.BoolLong(installPackagesFlagName, 'k', "Whether to install necessary packages. Default is false.")

	const ignoreUpdateFlagName = "ignore-update-flag"
	ignoreUpdateFlagPtr := getopt.BoolLong(ignoreUpdateFlagName, 'F', "Whether to ignore the upd_pending or reval_pending flag in Traffic Ops, and always generate and apply files. If true, the flag is still unset in Traffic Ops after files are applied. Default is false.")
	noUnsetUpdateFlagPtr := getopt.BoolLong("no-unset-update-flag", 'd', "Whether to not unset the update flag in Traffic Ops after applying files. This option makes it possible to generate test or debug configuration from a production Traffic Ops without un-setting queue or reval flags. Default is false.")

	const updateIPAllowFlagName = "update-ipallow"
	updateIPAllowPtr := getopt.BoolLong(updateIPAllowFlagName, 'A', "Whether ipallow file will be updated if necessary. This exists because ATS had a bug where reloading after changing ipallow would block everything. Default is false.")

	const useStrategiesFlagName = "use-strategies"
	const defaultUseStrategies = t3cutil.UseStrategiesFlagFalse
	useStrategiesPtr := getopt.EnumLong(useStrategiesFlagName, 0, []string{string(t3cutil.UseStrategiesFlagTrue), string(t3cutil.UseStrategiesFlagCore), string(t3cutil.UseStrategiesFlagCore), ""}, "", "[true | core| false] whether to generate config using strategies.yaml instead of parent.config. If true use the parent_select plugin, if 'core' use ATS core strategies, if false use parent.config.")

	const useLocalATSVersionFlagName = "local-ats-version"
	useLocalATSVersionPtr := getopt.BoolLong(useLocalATSVersionFlagName, 0, "[true | false] whether to use the local installed ATS version for config generation. If false, attempt to use the Server Package Parameter and fall back to ATS 5. If true and the local ATS version cannot be found, an error will be logged and the version set to ATS 5. Default is false")

	const runModeFlagName = "run-mode"
	runModePtr := getopt.StringLong(runModeFlagName, 'm', "", `[badass | report | revalidate | syncds] run mode. Optional, convenience flag which sets other flags for common usage scenarios.
syncds     keeps the defaults:
                --report-only=false
                --files=all
                --install-packages=false
                --service-action=reload
                --ignore-update-flag=false
                --update-ipallow=false
                --no-unset-update-flag=false
revalidate sets --files=reval
                --wait-for-parents=true
badass     sets --install-packages=true
                --service-action=restart
                --ignore-update-flag=true
                --update-ipallow=true
report     sets --report-only=true
                --no-unset-update-flag=true
                --silent

Note the 'syncds' settings are all the flag defaults. Hence, if no mode is set, the default is effectively 'syncds'.

If any of the related flags are also set, they override the mode's default behavior.`)

	getopt.Parse()

	// The mode is never exposed outside this function to prevent accidentally changing behavior based on it,
	// so we want to log what flags the mode set here, to aid debugging.
	// But we can't do that until the loggers are initialized.
	modeLogStrs := []string{}
	fatalLogStrs := []string{}
	if getopt.IsSet(runModeFlagName) {
		runMode := t3cutil.StrToMode(*runModePtr)
		if runMode == t3cutil.ModeInvalid {
			fatalLogStrs = append(fatalLogStrs, *runModePtr+" is an invalid mode.")
		}
		modeLogStrs = append(modeLogStrs, "t3c-apply is running in "+runMode.String()+" mode")
		switch runMode {
		case t3cutil.ModeSyncDS:
			// syncds flags are all the defaults, no need to change anything
		case t3cutil.ModeRevalidate:
			if !getopt.IsSet(filesFlagName) {
				modeLogStrs = append(modeLogStrs, runMode.String()+" setting --"+filesFlagName+"="+t3cutil.ApplyFilesFlagReval.String())
				*filesPtr = t3cutil.ApplyFilesFlagReval.String()
			}
			if !getopt.IsSet(waitForParentsFlagName) {
				modeLogStrs = append(modeLogStrs, runMode.String()+" setting --"+waitForParentsFlagName+"="+"true")
				*waitForParentsPtr = true
			}
		case t3cutil.ModeBadAss:
			if !getopt.IsSet(serviceActionFlagName) {
				modeLogStrs = append(modeLogStrs, runMode.String()+" setting --"+serviceActionFlagName+"="+t3cutil.ApplyServiceActionFlagRestart.String())
				*serviceActionPtr = t3cutil.ApplyServiceActionFlagRestart.String()
			}
			if !getopt.IsSet(installPackagesFlagName) {
				modeLogStrs = append(modeLogStrs, runMode.String()+" setting --"+installPackagesFlagName+"="+"true")
				*installPackagesPtr = true
			}
			if !getopt.IsSet(ignoreUpdateFlagName) {
				modeLogStrs = append(modeLogStrs, runMode.String()+" setting --"+ignoreUpdateFlagName+"="+"true")
				*ignoreUpdateFlagPtr = true
			}
			if !getopt.IsSet(updateIPAllowFlagName) {
				modeLogStrs = append(modeLogStrs, runMode.String()+" setting --"+updateIPAllowFlagName+"="+"true")
				*updateIPAllowPtr = true
			}
		case t3cutil.ModeReport:
			if !getopt.IsSet(reportOnlyFlagName) {
				modeLogStrs = append(modeLogStrs, runMode.String()+" setting --"+reportOnlyFlagName+"="+"true")
				*reportOnlyPtr = true
			}
			if !getopt.IsSet(ignoreUpdateFlagName) {
				modeLogStrs = append(modeLogStrs, runMode.String()+" setting --"+ignoreUpdateFlagName+"="+"true")
				*ignoreUpdateFlagPtr = true
			}
			if !getopt.IsSet(silentFlagName) {
				modeLogStrs = append(modeLogStrs, runMode.String()+" setting --"+silentFlagName+"="+"true")
				*silentPtr = true
			}
		}
	}

	switch *goDirectPtr {
	case "false", "true", "old":
		if !getopt.IsSet(goDirectFlagName) {
			modeLogStrs = append(modeLogStrs, goDirectFlagName+" not set using default 'old'")
		}
	default:
		modeLogStrs = append(modeLogStrs, *goDirectPtr+" is not a valid go-direct option setting default 'old'")
		*goDirectPtr = "old"
	}

	if *serviceActionPtr == "" {
		*serviceActionPtr = defaultServiceAction.String()
	}
	if *filesPtr == "" {
		*filesPtr = defaultFiles.String()
	}

	if !getopt.IsSet(useStrategiesFlagName) {
		*useStrategiesPtr = defaultUseStrategies.String()
	}

	logLocationError := log.LogLocationStderr
	logLocationWarn := log.LogLocationNull
	logLocationInfo := log.LogLocationNull
	logLocationDebug := log.LogLocationNull
	if *silentPtr {
		logLocationError = log.LogLocationNull
	} else {
		if *verbosePtr >= 1 {
			logLocationWarn = log.LogLocationStderr
		}
		if *verbosePtr >= 2 {
			logLocationInfo = log.LogLocationStderr
			logLocationDebug = log.LogLocationStderr // t3c only has 3 verbosity options: none (-s), error (default or --verbose=0), warning (-v), and info (-vv). Any code calling log.Debug is treated as Info.
		}
	}

	if *verbosePtr > 2 {
		fatalLogStrs = append(fatalLogStrs, "Too many verbose options. The maximum log verbosity level is 2 (-vv or --verbose=2) for errors (0), warnings (1), and info (2)")
	}

	var cacheHostName string
	if len(*cacheHostNamePtr) > 0 {
		cacheHostName = *cacheHostNamePtr
	} else {
		cacheHostName, err = os.Hostname()
		if err != nil {
			fatalLogStrs = append(fatalLogStrs, "Could not get the hostname from the O.S., please supply a hostname: "+err.Error())
		}
		// strings.Split always returns a slice with at least 1 element, so we don't need a len check
		cacheHostName = strings.Split(cacheHostName, ".")[0]
	}

	useGit := StrToUseGitFlag(*useGitStr)

	if useGit == UseGitInvalid {
		fatalLogStrs = append(fatalLogStrs, "Invalid git flag '"+*useGitStr+"'. Valid options are yes, no, auto.")
	}

	retries := *retriesPtr
	reverseProxyDisable := *reverseProxyDisablePtr
	skipOsCheck := *skipOSCheckPtr
	useStrategies := t3cutil.UseStrategiesFlag(*useStrategiesPtr)
	toInsecure := *toInsecurePtr
	toTimeoutMS := time.Millisecond * time.Duration(*toTimeoutMSPtr)
	toURL := *toURLPtr
	toUser := *toUserPtr
	toPass := *toPassPtr
	dnsLocalBind := *dnsLocalBindPtr
	maxmindLocation := *maxmindLocationPtr

	if *version {
		cfg := &Cfg{Version: appVersion, GitRevision: gitRevision}
		fmt.Println(cfg.AppVersion())
		os.Exit(0)
	} else if *help {
		Usage()
		return Cfg{}, nil
	}

	urlSourceStr := "argument" // for error messages
	if toURL == "" {
		urlSourceStr = "environment variable"
		toURL = os.Getenv("TO_URL")
	} else {
		os.Setenv("TO_URL", toURL)
	}
	if toUser == "" {
		toUser = os.Getenv("TO_USER")
	} else {
		os.Setenv("TO_USER", toUser)
	}
	if *toPassPtr == "" {
		toPass = os.Getenv("TO_PASS")
	} else {
		os.Setenv("TO_PASS", toPass)
	}

	rpmDBisOk := true
	rpmDBType, err := getRpmDBBackend()
	if err != nil {
		toInfoLog = append(toInfoLog, fmt.Sprintf("error getting db type: %s", err.Error()))
		rpmDBType = rpmDBUnknown
	}

	if rpmDBType == rpmDBSquLite {
		sqliteUtil := isSqliteInstalled()
		toInfoLog = append(toInfoLog, fmt.Sprintf("RPM database is %s", rpmDBSquLite))
		if sqliteUtil {
			rpmDBisOk = verifySqliteRpmDB(rpmDir + "/" + sqliteRpmDB)
			toInfoLog = append(toInfoLog, fmt.Sprintf("RPM database is ok: %t", rpmDBisOk))
		} else {
			toInfoLog = append(toInfoLog, "/bin/sqlite3 not available, RPM database not checked")
		}
	} else if rpmDBType == rpmDBBdb {
		toInfoLog = append(toInfoLog, fmt.Sprintf("RPM database is %s", rpmDBBdb))
		rpmDBisOk = verifyRpmDB(rpmDir)
		toInfoLog = append(toInfoLog, fmt.Sprintf("RPM database is ok: %t", rpmDBisOk))
	} else {
		toInfoLog = append(toInfoLog, fmt.Sprintf("RPM DB type is %s DB check will be skipped", rpmDBUnknown))
	}

	if *installPackagesPtr && !rpmDBisOk {
		if t3cutil.StrToMode(*runModePtr) == t3cutil.ModeBadAss {
			fatalLogStrs = append(fatalLogStrs, "RPM database check failed unable to install packages cannot continue in badass mode")
		} else {
			fatalLogStrs = append(fatalLogStrs, "RPM database check failed unable to install packages cannot continue")
		}
	}

	// set TSHome
	var tsHome = ""
	if *tsHomePtr != "" {
		tsHome = *tsHomePtr
		toInfoLog = append(toInfoLog, fmt.Sprintf("set TSHome from command line: '%s'", TSHome))
	}
	if *tsHomePtr == "" { // evironment or rpm check.
		tsHome = os.Getenv("TS_HOME") // check for the environment variable.
		if tsHome != "" {
			toInfoLog = append(toInfoLog, fmt.Sprintf("set TSHome from TS_HOME environment variable '%s'\n", TSHome))
		} else if rpmDBisOk { // check using the config file listing from the rpm package if rpmdb is ok.
			tsHome = GetTSPackageHome()
			if tsHome != "" {
				toInfoLog = append(toInfoLog, fmt.Sprintf("set TSHome from the RPM config file  list '%s'\n", TSHome))
			}
		} else if tsHome == "" {
			toInfoLog = append(toInfoLog, fmt.Sprintf("no override for TSHome was found, using the configured default: '%s'\n", TSHome))
		}
	}

	tsConfigDir := DefaultTSConfigDir

	if tsHome != "" {
		TSHome = tsHome
		tsConfigDir = tsHome + "/etc/trafficserver"
		if cache != nil && *cache == "varnish" {
			tsConfigDir = tsHome + "/etc/varnish"
		}
		toInfoLog = append(toInfoLog, fmt.Sprintf("TSHome: %s, TSConfigDir: %s\n", TSHome, tsConfigDir))
	}

	atsVersionStr := ""
	if *useLocalATSVersionPtr {
		atsVersionStr, err = GetATSVersionStr(tsHome)
		if err != nil {
			fatalLogStrs = append(fatalLogStrs, "getting local ATS version: "+err.Error())
		}
	}
	toInfoLog = append(toInfoLog, fmt.Sprintf("ATSVersionStr: '%s'\n", atsVersionStr))

	usageStr := "basic usage: t3c-apply --traffic-ops-url=myurl --traffic-ops-user=myuser --traffic-ops-password=mypass --cache-host-name=my-cache"
	if strings.TrimSpace(toURL) == "" {
		fatalLogStrs = append(fatalLogStrs, "Missing required argument --traffic-ops-url or TO_URL environment variable. "+usageStr)
	}
	if strings.TrimSpace(toUser) == "" {
		fatalLogStrs = append(fatalLogStrs, "Missing required argument --traffic-ops-user or TO_USER environment variable. "+usageStr)
	}
	if strings.TrimSpace(toPass) == "" {
		fatalLogStrs = append(fatalLogStrs, "Missing required argument --traffic-ops-password or TO_PASS environment variable. "+usageStr)
	}
	if strings.TrimSpace(cacheHostName) == "" {
		fatalLogStrs = append(fatalLogStrs, "Missing required argument --cache-host-name. "+usageStr)
	}

	toURLParsed, err := url.Parse(toURL)
	if err != nil {
		return Cfg{}, errors.New("parsing Traffic Ops URL from " + urlSourceStr + " '" + toURL + "': " + err.Error())
	} else if err = validateURL(toURLParsed); err != nil {
		return Cfg{}, errors.New("invalid Traffic Ops URL from " + urlSourceStr + " '" + toURL + "': " + err.Error())
	}

	svcManagement := getOSSvcManagement()
	yumOptions := os.Getenv("YUM_OPTIONS")

	cfg := Cfg{
		LogLocationDebug:            logLocationDebug,
		LogLocationErr:              logLocationError,
		LogLocationInfo:             logLocationInfo,
		LogLocationWarn:             logLocationWarn,
		CacheHostName:               cacheHostName,
		SvcManagement:               svcManagement,
		Retries:                     retries,
		RpmDBOk:                     rpmDBisOk,
		ReverseProxyDisable:         reverseProxyDisable,
		SkipOSCheck:                 skipOsCheck,
		UseStrategies:               useStrategies,
		TOInsecure:                  toInsecure,
		TOTimeoutMS:                 toTimeoutMS,
		TOUser:                      toUser,
		TOPass:                      toPass,
		TOURL:                       toURL,
		DNSLocalBind:                dnsLocalBind,
		WaitForParents:              *waitForParentsPtr,
		YumOptions:                  yumOptions,
		UseGit:                      useGit,
		NoCache:                     *noCachePtr,
		SyncDSUpdatesIPAllow:        *syncdsUpdatesIPAllowPtr,
		UpdateIPAllow:               *updateIPAllowPtr,
		OmitViaStringRelease:        *omitViaStringReleasePtr,
		NoOutgoingIP:                *noOutgoingIP,
		DisableParentConfigComments: *disableParentConfigCommentsPtr,
		DefaultClientEnableH2:       defaultEnableH2,
		DefaultClientTLSVersions:    defaultClientTLSVersions,
		MaxMindLocation:             maxmindLocation,
		TsHome:                      TSHome,
		TsConfigDir:                 tsConfigDir,
		GoDirect:                    *goDirectPtr,
		ServiceAction:               t3cutil.ApplyServiceActionFlag(*serviceActionPtr),
		NoConfirmServiceAction:      *noConfirmServiceAction,
		ReportOnly:                  *reportOnlyPtr,
		Files:                       t3cutil.ApplyFilesFlag(*filesPtr),
		InstallPackages:             *installPackagesPtr,
		IgnoreUpdateFlag:            *ignoreUpdateFlagPtr,
		NoUnsetUpdateFlag:           *noUnsetUpdateFlagPtr,
		Version:                     appVersion,
		GitRevision:                 gitRevision,
		LocalATSVersion:             atsVersionStr,
		CacheType:                   *cache,
	}

	if err = log.InitCfg(cfg); err != nil {
		return Cfg{}, errors.New("Initializing loggers: " + err.Error() + "\n")
	}

	if len(fatalLogStrs) > 0 {
		for _, str := range fatalLogStrs {
			str = strings.TrimSpace(str)
			log.Errorln(str)
		}
		return Cfg{}, errors.New("fatal error has occurred")
	}
	for _, str := range modeLogStrs {
		str = strings.TrimSpace(str)
		if str == "" {
			continue
		}
		log.Infoln(str)
	}
	for _, msg := range toInfoLog {
		msg = strings.TrimSpace(msg)
		if msg == "" {
			continue
		}
		log.Infoln(msg)
	}

	printConfig(cfg)

	return cfg, nil
}

func validateURL(u *url.URL) error {
	if u == nil {
		return errors.New("nil url")
	}
	if u.Scheme != "http" && u.Scheme != "https" {
		return errors.New("scheme expected 'http' or 'https', actual '" + u.Scheme + "'")
	}
	if strings.TrimSpace(u.Host) == "" {
		return errors.New("no host")
	}
	return nil
}

func isCommandAvailable(name string) bool {
	command := name + " --version"
	cmd := exec.Command("/bin/sh", "-c", command)
	if err := cmd.Run(); err != nil {
		return false
	}

	return true
}

func getOSSvcManagement() SvcManagement {
	var _svcManager SvcManagement

	if isCommandAvailable(SystemCtl) {
		_svcManager = SystemD
	} else if isCommandAvailable(Service) {
		_svcManager = SystemV
	} else {
		return Unknown
	}

	// we have what we need
	return _svcManager
}

func GetATSVersionStr(tsHome string) (string, error) {
	tsPath := tsHome
	tsPath = filepath.Join(tsPath, "bin")
	tsPath = filepath.Join(tsPath, "traffic_server")

	stdOut, stdErr, code := t3cutil.Do(`sh`, `-c`, `set -o pipefail && `+tsPath+` --version | head -1 | awk '{print $3}'`)
	if code != 0 {
		return "", fmt.Errorf("traffic_server --version returned code %v stderr '%v' stdout '%v'", code, string(stdErr), string(stdOut))
	}
	atsVersion := strings.TrimSpace(string(stdOut))
	if atsVersion == "" {
		return "", fmt.Errorf("traffic_server --version returned nothing, code %v stderr '%v' stdout '%v'", code, string(stdErr), string(stdOut))
	}
	return atsVersion, nil
}

func printConfig(cfg Cfg) {
	// TODO add new flags
	log.Debugf("LogLocationDebug: %s\n", cfg.LogLocationDebug)
	log.Debugf("LogLocationErr: %s\n", cfg.LogLocationErr)
	log.Debugf("LogLocationInfo: %s\n", cfg.LogLocationInfo)
	log.Debugf("LogLocationWarn: %s\n", cfg.LogLocationWarn)
	log.Debugf("CacheHostName: %s\n", cfg.CacheHostName)
	log.Debugf("SvcManagement: %s\n", cfg.SvcManagement)
	log.Debugf("GoDirect: %s\n", cfg.GoDirect)
	log.Debugf("Retries: %d\n", cfg.Retries)
	log.Debugf("ReverseProxyDisable: %t\n", cfg.ReverseProxyDisable)
	log.Debugf("SkipOSCheck: %t\n", cfg.SkipOSCheck)
	log.Debugf("TOInsecure: %t\n", cfg.TOInsecure)
	log.Debugf("TOTimeoutMS: %d\n", cfg.TOTimeoutMS)
	log.Debugf("TOUser: %s\n", cfg.TOUser)
	log.Debugf("TOPass: Pass len: '%d'\n", len(cfg.TOPass))
	log.Debugf("TOURL: %s\n", cfg.TOURL)
	log.Debugf("TSHome: %s\n", TSHome)
	log.Debugf("LocalATSVersion: %s\n", cfg.LocalATSVersion)
	log.Debugf("WaitForParents: %v\n", cfg.WaitForParents)
	log.Debugf("ServiceAction: %v\n", cfg.ServiceAction)
	log.Debugf("NoConfirmServiceAction: %v\n", cfg.NoConfirmServiceAction)
	log.Debugf("YumOptions: %s\n", cfg.YumOptions)
	log.Debugf("MaxmindLocation: %s\n", cfg.MaxMindLocation)
}

func Usage() {
	getopt.PrintUsage(os.Stdout)
}
