package service

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"runtime"

	"github.com/safing/jess"
	"github.com/safing/portmaster/base/log"
	"github.com/safing/portmaster/service/configure"
	"github.com/safing/portmaster/service/updates"
)

type ServiceConfig struct {
	BinDir  string
	DataDir string

	LogToStdout bool
	LogDir      string
	LogLevel    string

	BinariesIndexURLs   []string
	IntelIndexURLs      []string
	VerifyBinaryUpdates jess.TrustStore
	VerifyIntelUpdates  jess.TrustStore
}

func (sc *ServiceConfig) Init() error {
	// Check directories.
	switch runtime.GOOS {
	case "windows":
		// Fall back to defaults.
		if sc.BinDir == "" {
			exeDir, err := getCurrentBinaryFolder() // Default: C:/Program Files/Portmaster
			if err != nil {
				return fmt.Errorf("derive bin dir from running exe: %w", err)
			}
			sc.BinDir = exeDir
		}
		if sc.DataDir == "" {
			sc.DataDir = filepath.FromSlash("$ProgramData/Portmaster")
		}
		if sc.LogDir == "" {
			sc.LogDir = filepath.Join(sc.DataDir, "logs")
		}

	case "linux":
		// Fall back to defaults.
		if sc.BinDir == "" {
			sc.BinDir = "/usr/lib/portmaster"
		}
		if sc.DataDir == "" {
			sc.DataDir = "/var/lib/portmaster"
		}
		if sc.LogDir == "" {
			sc.LogDir = "/var/log/portmaster"
		}

	default:
		// Fail if not configured on other platforms.
		if sc.BinDir == "" {
			return errors.New("binary directory must be configured - auto-detection not supported on this platform")
		}
		if sc.DataDir == "" {
			return errors.New("binary directory must be configured - auto-detection not supported on this platform")
		}
		if !sc.LogToStdout && sc.LogDir == "" {
			return errors.New("logging directory must be configured - auto-detection not supported on this platform")
		}
	}

	// Expand path variables.
	sc.BinDir = os.ExpandEnv(sc.BinDir)
	sc.DataDir = os.ExpandEnv(sc.DataDir)
	sc.LogDir = os.ExpandEnv(sc.LogDir)

	// Apply defaults for required fields.
	if len(sc.BinariesIndexURLs) == 0 {
		sc.BinariesIndexURLs = configure.DefaultStableBinaryIndexURLs
	}
	if len(sc.IntelIndexURLs) == 0 {
		sc.IntelIndexURLs = configure.DefaultIntelIndexURLs
	}

	// Check log level.
	if sc.LogLevel != "" && log.ParseLevel(sc.LogLevel) == 0 {
		return fmt.Errorf("invalid log level %q", sc.LogLevel)
	}

	return nil
}

// returns the absolute path of the currently running executable
func getCurrentBinaryPath() (string, error) {
	// Get the path of the currently running executable
	exePath, err := os.Executable()
	if err != nil {
		return "", fmt.Errorf("failed to get executable path: %w", err)
	}

	// Get the absolute path
	absPath, err := filepath.Abs(exePath)
	if err != nil {
		return "", fmt.Errorf("failed to get absolute path: %w", err)
	}

	return absPath, nil
}

func getCurrentBinaryFolder() (string, error) {
	// Get the absolute path of the currently running executable
	absPath, err := getCurrentBinaryPath()
	if err != nil {
		return "", err
	}

	// Get the directory of the executable
	installDir := filepath.Dir(absPath)

	return installDir, nil
}

func MakeUpdateConfigs(svcCfg *ServiceConfig) (binaryUpdateConfig, intelUpdateConfig *updates.Config, err error) {
	switch runtime.GOOS {
	case "windows":
		binaryUpdateConfig = &updates.Config{
			Name:              configure.DefaultBinaryIndexName,
			Directory:         svcCfg.BinDir,
			DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_binaries"),
			PurgeDirectory:    filepath.Join(svcCfg.BinDir, "upgrade_obsolete_binaries"),
			Ignore:            []string{"uninstall.exe"}, // "databases", "intel" and "config.json" not needed here since they are not in the bin dir.
			IndexURLs:         svcCfg.BinariesIndexURLs,  // May be changed by config during instance startup.
			IndexFile:         "index.json",
			Verify:            svcCfg.VerifyBinaryUpdates,
			AutoCheck:         true, // May be changed by config during instance startup.
			AutoDownload:      false,
			AutoApply:         false,
			NeedsRestart:      true,
			Notify:            true,
		}
		intelUpdateConfig = &updates.Config{
			Name:              configure.DefaultIntelIndexName,
			Directory:         filepath.Join(svcCfg.DataDir, "intel"),
			DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_intel"),
			PurgeDirectory:    filepath.Join(svcCfg.DataDir, "upgrade_obsolete_intel"),
			IndexURLs:         svcCfg.IntelIndexURLs,
			IndexFile:         "index.json",
			Verify:            svcCfg.VerifyIntelUpdates,
			AutoCheck:         true, // May be changed by config during instance startup.
			AutoDownload:      true,
			AutoApply:         true,
			NeedsRestart:      false,
			Notify:            false,
		}

	case "linux":
		binaryUpdateConfig = &updates.Config{
			Name:              configure.DefaultBinaryIndexName,
			Directory:         svcCfg.BinDir,
			DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_binaries"),
			PurgeDirectory:    filepath.Join(svcCfg.DataDir, "upgrade_obsolete_binaries"),
			Ignore:            []string{},               // "databases", "intel" and "config.json" not needed here since they are not in the bin dir.
			IndexURLs:         svcCfg.BinariesIndexURLs, // May be changed by config during instance startup.
			IndexFile:         "index.json",
			Verify:            svcCfg.VerifyBinaryUpdates,
			AutoCheck:         true, // May be changed by config during instance startup.
			AutoDownload:      false,
			AutoApply:         false,
			NeedsRestart:      true,
			Notify:            true,
		}
		if binPath, err := getCurrentBinaryPath(); err == nil {
			binaryUpdateConfig.PostUpgradeCommands = []updates.UpdateCommandConfig{
				// Restore SELinux context for the new core binary after upgrade
				// (`restorecon /usr/lib/portmaster/portmaster-core`)
				{
					Command:              "restorecon",
					Args:                 []string{binPath},
					TriggerArtifactFName: binPath,
					FailOnError:          false, // Ignore error: 'restorecon' may not be available on a non-SELinux systems.
				},
			}
		} else {
			return nil, nil, fmt.Errorf("failed to get current binary path: %w", err)
		}

		intelUpdateConfig = &updates.Config{
			Name:              configure.DefaultIntelIndexName,
			Directory:         filepath.Join(svcCfg.DataDir, "intel"),
			DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_intel"),
			PurgeDirectory:    filepath.Join(svcCfg.DataDir, "upgrade_obsolete_intel"),
			IndexURLs:         svcCfg.IntelIndexURLs,
			IndexFile:         "index.json",
			Verify:            svcCfg.VerifyIntelUpdates,
			AutoCheck:         true, // May be changed by config during instance startup.
			AutoDownload:      true,
			AutoApply:         true,
			NeedsRestart:      false,
			Notify:            false,
		}
	}

	return
}
