package setup

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/safedep/dry/cloud"
	"github.com/safedep/dry/log"
	"github.com/safedep/pmg/config"
	"github.com/safedep/pmg/internal/alias"
	"github.com/safedep/pmg/internal/analytics"
	"github.com/safedep/pmg/internal/audit"
	"github.com/safedep/pmg/internal/shim"
	"github.com/safedep/pmg/internal/ui"
	"github.com/safedep/pmg/internal/version"
	"github.com/safedep/pmg/proxy/certmanager"
	"github.com/safedep/pmg/sandbox/platform"
	"github.com/safedep/pmg/truststore"
	"github.com/spf13/cobra"
)

func NewInfoCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "info",
		Short: "Show information about PMG setup and configuration.",
		RunE: func(cmd *cobra.Command, args []string) error {
			err := executeSetupInfo()
			if err != nil {
				ui.ErrorExit(fmt.Errorf("failed to execute setup info: %w", err))
			}

			return nil
		},
	}

	return cmd
}

func executeSetupInfo() error {
	fmt.Print(ui.GeneratePMGBanner(version.Version, version.Commit))

	// Configuration section
	cfg := config.Get()
	configEntries := make(map[string]string)
	configEntries["Config File"] = cfg.ConfigFilePath()
	configSource := "user"
	if cfg.IsManaged() {
		configSource = "global"
		if cfg.IsLocked() {
			configSource = "global (locked)"
		}
	}
	configEntries["Config Source"] = configSource
	configEntries["Proxy Mode"] = strconv.FormatBool(cfg.IsProxyModeEnabled())
	configEntries["Proxy Install Only"] = strconv.FormatBool(cfg.Config.Proxy.InstallOnly)
	ui.PrintInfoSection("Configuration", configEntries)

	// Shell Integration section
	aliasCfg := alias.DefaultConfig()
	rcFileManager, err := alias.NewDefaultRcFileManager(aliasCfg.RcFileName)
	if err != nil {
		return fmt.Errorf("failed to create alias manager: %w", err)
	}

	aliasManager := alias.New(aliasCfg, rcFileManager)
	isInstalled, err := aliasManager.IsInstalled()
	if err != nil {
		isInstalled = false
	}

	shellEntries := make(map[string]string)
	shell, err := alias.DetectShell()
	if err != nil {
		shell = "unknown"
	}

	shellEntries["Detected Shell"] = shell
	shellEntries["Alias Installed"] = strconv.FormatBool(isInstalled)
	shellEntries["User Shims"] = strconv.FormatBool(shim.UserShimsInstalled())
	if shim.SystemShimsInstalled() {
		shellEntries["System Shims"] = shim.SystemBinDir()
	} else {
		shellEntries["System Shims"] = "not installed"
	}
	ui.PrintInfoSection("Shell Integration", shellEntries)

	// Security section
	securityEntries := make(map[string]string)
	trustedPackages := cfg.Config.TrustedPackages
	trustedPackagesCount := len(trustedPackages)

	if trustedPackagesCount > 3 {
		purls := []string{}
		for _, p := range trustedPackages[0:3] {
			purls = append(purls, p.Purl)
		}

		trustedPackagesValue := fmt.Sprintf("%v ...and %d more", purls, trustedPackagesCount-3)
		securityEntries["Trusted Packages"] = trustedPackagesValue
	} else if trustedPackagesCount > 0 {
		purls := []string{}
		for _, p := range trustedPackages {
			purls = append(purls, p.Purl)
		}

		securityEntries["Trusted Packages"] = fmt.Sprintf("%v", purls)
	} else {
		securityEntries["Trusted Packages"] = "None"
	}

	securityEntries["Dependency Cooldown"] = strconv.FormatBool(cfg.Config.DependencyCooldown.Enabled)
	securityEntries["Dependency Cooldown Days"] = strconv.Itoa(cfg.Config.DependencyCooldown.Days)
	securityEntries["Telemetry"] = strconv.FormatBool(!analytics.IsDisabled())
	securityEntries["Event Logging"] = strconv.FormatBool(!cfg.Config.SkipEventLogging)
	securityEntries["Event Log Directory"] = cfg.EventLogDir()

	ui.PrintInfoSection("Security", securityEntries)

	// Sandbox section
	sandboxCfg := cfg.Config.Sandbox
	sandboxEntries := make(map[string]string)
	sandboxEntries["Enabled"] = strconv.FormatBool(sandboxCfg.Enabled)
	sandboxEntries["Enforce Always"] = strconv.FormatBool(sandboxCfg.EnforceAlways)
	sandboxEntries["Driver"] = resolveSandboxDriverName()

	if len(sandboxCfg.Policies) > 0 {
		pmNames := make([]string, 0, len(sandboxCfg.Policies))
		for name := range sandboxCfg.Policies {
			pmNames = append(pmNames, name)
		}

		sort.Strings(pmNames)

		policyParts := make([]string, 0, len(pmNames))
		for _, name := range pmNames {
			ref, _ := sandboxCfg.PolicyFor(name)
			status := "disabled"
			if ref.Enabled {
				status = ref.Profile
			}

			policyParts = append(policyParts, fmt.Sprintf("%s(%s)", name, status))
		}

		sandboxEntries["Policies"] = strings.Join(policyParts, ", ")
	} else {
		sandboxEntries["Policies"] = "None"
	}

	ui.PrintInfoSection("Sandbox", sandboxEntries)

	// Certificate Authority section
	caStatus, _ := certmanager.InspectCA(cfg.ConfigDir())
	caUser, caSystem, _ := truststore.Status(certmanager.CACommonName)
	caStatus.UserTrusted, caStatus.SystemTrusted = caUser, caSystem

	caEntries := make(map[string]string)
	caEntries["Installed"] = strconv.FormatBool(caStatus.KeyPresent && caStatus.CertPresent)
	caScope := "none"
	if caStatus.SystemTrusted {
		caScope = "system"
	} else if caStatus.UserTrusted {
		caScope = "user"
	}
	caEntries["Trust Scope"] = caScope
	if caStatus.CertPresent {
		caEntries["Expires"] = caStatus.NotAfter.Format("2006-01-02")
		caEntries["Fingerprint"] = caStatus.Fingerprint
	}
	ui.PrintInfoSection("Certificate Authority", caEntries)

	if cfg.Config.Cloud.Enabled {
		cloudEntries := make(map[string]string)
		cloudEntries["Enabled"] = "true"
		cloudEntries["Sync DB"] = cfg.CloudSyncDBPath()
		if cfg.Config.Cloud.EndpointID != "" {
			cloudEntries["Endpoint ID"] = cfg.Config.Cloud.EndpointID
		}
		cloudEntries["Credentials"] = describeCloudCredentials()
		cloudEntries["Auto Sync"] = describeAutoSync(cfg.Config.Cloud.AutoSync)
		cloudEntries["Last Sync"] = describeLastSync(cfg.CloudSyncLastRunPath())
		ui.PrintInfoSection("Cloud Sync", cloudEntries)
	}

	return nil
}

func resolveSandboxDriverName() string {
	sb, err := platform.NewSandbox()
	if err != nil {
		return "unavailable"
	}

	if !sb.IsAvailable() {
		return "unavailable"
	}

	return string(sb.Name())
}

// describeCloudCredentials reports whether SafeDep Cloud credentials can be
// resolved, and from where. The resolution order matches NewSyncClientBundle:
// keychain first, then environment variables. No network calls are made.
func describeCloudCredentials() string {
	if source, ok := tryResolveKeychainCredentials(); ok {
		return source
	}
	if source, ok := tryResolveEnvCredentials(); ok {
		return source
	}
	return "not configured (run 'pmg cloud login' or set SAFEDEP_API_KEY and SAFEDEP_TENANT_ID)"
}

func tryResolveKeychainCredentials() (string, bool) {
	resolver, err := cloud.NewKeychainCredentialResolver(cloud.CredentialTypeAPIKey)
	if err != nil {
		log.Debugf("keychain credential resolver unavailable: %v", err)
		return "", false
	}
	defer func() {
		if err := resolver.Close(); err != nil {
			log.Warnf("failed to close keychain resolver: %v", err)
		}
	}()

	if _, err := resolver.Resolve(); err != nil {
		log.Debugf("no keychain credentials: %v", err)
		return "", false
	}
	return "keychain", true
}

func describeAutoSync(c config.CloudAutoSyncConfig) string {
	if !c.Enabled {
		return "disabled"
	}
	return fmt.Sprintf("enabled (every %s, timeout %s)", c.MinInterval, c.Timeout)
}

// describeLastSync renders a missing or unparseable timestamp as "never" so a
// fresh install does not look broken.
func describeLastSync(path string) string {
	last := audit.ReadLastSyncAttempt(path)
	if last.IsZero() {
		return "never"
	}

	delta := time.Since(last)
	switch {
	case delta < time.Minute:
		return "just now"
	case delta < time.Hour:
		return fmt.Sprintf("%d minutes ago", int(delta.Minutes()))
	case delta < 24*time.Hour:
		return fmt.Sprintf("%d hours ago", int(delta.Hours()))
	default:
		return fmt.Sprintf("%d days ago", int(delta.Hours()/24))
	}
}

func tryResolveEnvCredentials() (string, bool) {
	resolver, err := cloud.NewEnvCredentialResolver()
	if err != nil {
		log.Debugf("env credential resolver unavailable: %v", err)
		return "", false
	}
	if _, err := resolver.Resolve(); err != nil {
		log.Debugf("no env credentials: %v", err)
		return "", false
	}
	return "environment (SAFEDEP_API_KEY, SAFEDEP_TENANT_ID)", true
}
