package cli

import (
	"context"
	"errors"
	"fmt"
	"os"
	"strings"
	"time"

	"banger/internal/config"
	"banger/internal/installmeta"
	"banger/internal/model"
	"banger/internal/paths"
)

const (
	daemonReadyTimeout      = 15 * time.Second
	daemonReadyPollInterval = 100 * time.Millisecond
)

// waitForDaemonReady blocks until the daemon at socketPath answers
// ping, the context is cancelled, or daemonReadyTimeout elapses.
// Used by `system install` and `system restart` so they don't return
// before the daemon has actually finished binding its socket — the
// systemd Type=simple unit reports "active" the moment the binary
// is exec()'d, well before bangerd has read its config and listened
// on the unix socket.
func (d *deps) waitForDaemonReady(ctx context.Context, socketPath string) error {
	deadline := time.Now().Add(daemonReadyTimeout)
	pingCtx, cancel := context.WithDeadline(ctx, deadline)
	defer cancel()
	for {
		if _, err := d.daemonPing(pingCtx, socketPath); err == nil {
			return nil
		}
		if time.Now().After(deadline) {
			return fmt.Errorf("daemon did not become ready at %s within %s", socketPath, daemonReadyTimeout)
		}
		select {
		case <-pingCtx.Done():
			return fmt.Errorf("daemon did not become ready at %s: %w", socketPath, pingCtx.Err())
		case <-time.After(daemonReadyPollInterval):
		}
	}
}

var (
	loadInstallMetadata = func() (installmeta.Metadata, error) {
		return installmeta.Load(installmeta.DefaultPath)
	}
	currentUID = os.Getuid
)

// ensureDaemon validates that the current CLI user matches the
// installed banger owner, then pings the system socket. Every CLI
// command that needs to talk to the daemon routes through here.
func (d *deps) ensureDaemon(ctx context.Context) (paths.Layout, model.DaemonConfig, error) {
	meta, metaErr := loadInstallMetadata()
	if metaErr == nil && currentUID() != meta.OwnerUID {
		return paths.Layout{}, model.DaemonConfig{}, fmt.Errorf("banger is installed for %s; switch to that user or reinstall with `sudo banger system install --owner %s`", meta.OwnerUser, userHint())
	}
	if metaErr != nil && !errors.Is(metaErr, os.ErrNotExist) {
		return paths.Layout{}, model.DaemonConfig{}, fmt.Errorf("load %s: %w", installmeta.DefaultPath, metaErr)
	}

	userLayout, err := paths.Resolve()
	if err != nil {
		return paths.Layout{}, model.DaemonConfig{}, err
	}
	cfg, err := config.Load(userLayout)
	if err != nil {
		return paths.Layout{}, model.DaemonConfig{}, err
	}
	layout := paths.ResolveSystem()
	if _, err := d.daemonPing(ctx, layout.SocketPath); err == nil {
		return layout, cfg, nil
	}
	if metaErr == nil {
		return paths.Layout{}, model.DaemonConfig{}, fmt.Errorf("banger service not reachable at %s; run `sudo banger system restart`", layout.SocketPath)
	}
	return paths.Layout{}, model.DaemonConfig{}, fmt.Errorf("banger service not running at %s; run `sudo banger system install`", layout.SocketPath)
}

func userHint() string {
	if sudoUser := strings.TrimSpace(os.Getenv("SUDO_USER")); sudoUser != "" {
		return sudoUser
	}
	if user := strings.TrimSpace(os.Getenv("USER")); user != "" {
		return user
	}
	return "<user>"
}
