package wol

import (
	"context"
	"fmt"
	"net"
	"os/exec"
	"time"
)

var ErrTimeout = fmt.Errorf("timeout waiting for machine to respond")

func WaitUntilReady(ctx context.Context, host string, sshPort int, maxWait, checkInterval time.Duration, usePing bool) error {
	deadline := time.Now().Add(maxWait)
	interval := checkInterval

	ticker := time.NewTicker(checkInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-ticker.C:
		}

		addr := fmt.Sprintf("%s:%d", host, sshPort)
		dialer := net.Dialer{Timeout: 3 * time.Second}
		conn, err := dialer.DialContext(ctx, "tcp", addr)
		if err == nil {
			conn.Close()
			return nil
		}

		if usePing {
			cmd := exec.CommandContext(ctx, "ping", "-c", "1", "-W", "1", host)
			if err := cmd.Run(); err == nil {
				return nil
			}
		}

		if time.Now().After(deadline) {
			return ErrTimeout
		}

		elapsed := time.Since(time.Now().Add(-maxWait))
		if elapsed > maxWait/2 && interval < 10*time.Second {
			interval = interval * 3 / 2
			if interval > 10*time.Second {
				interval = 10 * time.Second
			}
			ticker.Reset(interval)
		}
	}
}

func IsHostReachable(host string, port int, timeout time.Duration) bool {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()
	addr := fmt.Sprintf("%s:%d", host, port)
	dialer := net.Dialer{Timeout: timeout}
	conn, err := dialer.DialContext(ctx, "tcp", addr)
	if err == nil {
		conn.Close()
		return true
	}
	return false
}
