package ntp

import (
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/beevik/ntp"

	"github.com/acepanel/panel/pkg/shell"
)

var ErrNotReachable = errors.New("failed to reach NTP server")

var ErrNoAvailableServer = errors.New("no available NTP server found")

var defaultAddresses = []string{
	//"ntp.ntsc.ac.cn",      // 中科院国家授时中心的服务器很快，但是多刷几次就会被封
	"ntp.aliyun.com",   // 阿里云
	"ntp1.aliyun.com",  // 阿里云2
	"ntp.tencent.com",  // 腾讯云
	"time.windows.com", // Windows
	"time.apple.com",   // Apple
}

func Now(address ...string) (time.Time, error) {
	if len(address) > 0 {
		if now, err := ntp.Time(address[0]); err != nil {
			return time.Now(), fmt.Errorf("%w: %s", ErrNotReachable, err)
		} else {
			return now, nil
		}
	}

	best, err := bestServer(defaultAddresses...)
	if err != nil {
		return time.Now(), err
	}

	now, err := ntp.Time(best)
	if err != nil {
		return time.Now(), fmt.Errorf("%w: %s", ErrNotReachable, err)
	}

	return now, nil
}

func UpdateSystemTime(t time.Time) error {
	_, err := shell.Execf(`date -s '%s'`, t.Format(time.DateTime))
	return err
}

func UpdateSystemTimeZone(tz string) error {
	_, err := shell.Execf(`timedatectl set-timezone '%s'`, tz)
	return err
}

// pingServer 计算NTP服务器的延迟
func pingServer(addr string) (time.Duration, error) {
	options := ntp.QueryOptions{Timeout: 1 * time.Second}
	response, err := ntp.QueryWithOptions(addr, options)
	if err != nil {
		return 0, err
	}

	return response.RTT, nil
}

// bestServer 返回延迟最低的NTP服务器
func bestServer(addresses ...string) (string, error) {
	if len(addresses) == 0 {
		addresses = defaultAddresses
	}

	type ntpResult struct {
		address string
		delay   time.Duration
		err     error
	}

	results := make(chan ntpResult, len(addresses))
	var wg sync.WaitGroup

	for _, addr := range addresses {
		wg.Add(1)
		go func(addr string) {
			defer wg.Done()

			delay, err := pingServer(addr)
			results <- ntpResult{address: addr, delay: delay, err: err}
		}(addr)
	}

	wg.Wait()
	close(results)

	var bestAddr string
	var bestDelay time.Duration
	found := false

	for result := range results {
		if result.err != nil {
			continue
		}

		if !found || result.delay < bestDelay {
			bestAddr = result.address
			bestDelay = result.delay
			found = true
		}
	}

	if !found {
		return "", ErrNoAvailableServer
	}

	return bestAddr, nil
}
