// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause

// Package tstime defines Tailscale-specific time utilities.
package tstime

import (
	"context"
	"strconv"
	"strings"
	"time"
)

// Parse3339 is a wrapper around time.Parse(time.RFC3339, s).
func Parse3339(s string) (time.Time, error) {
	return time.Parse(time.RFC3339, s)
}

// Parse3339B is Parse3339 but for byte slices.
func Parse3339B(b []byte) (time.Time, error) {
	var t time.Time
	if err := t.UnmarshalText(b); err != nil {
		return Parse3339(string(b)) // reproduce same error message
	}
	return t, nil
}

// ParseDuration is more expressive than [time.ParseDuration],
// also accepting 'd' (days) and 'w' (weeks) literals.
func ParseDuration(s string) (time.Duration, error) {
	for {
		end := strings.IndexAny(s, "dw")
		if end < 0 {
			break
		}
		start := end - (len(s[:end]) - len(strings.TrimRight(s[:end], "0123456789")))
		n, err := strconv.Atoi(s[start:end])
		if err != nil {
			return 0, err
		}
		hours := 24
		if s[end] == 'w' {
			hours *= 7
		}
		s = s[:start] + s[end+1:] + strconv.Itoa(n*hours) + "h"
	}
	return time.ParseDuration(s)
}

// Sleep is like [time.Sleep] but returns early upon context cancelation.
// It reports whether the full sleep duration was achieved.
func Sleep(ctx context.Context, d time.Duration) bool {
	timer := time.NewTimer(d)
	defer timer.Stop()
	select {
	case <-ctx.Done():
		return false
	case <-timer.C:
		return true
	}
}

// Clock offers a subset of the functionality from the std/time package.
// Normally, applications will use the StdClock implementation that calls the
// appropriate std/time exported funcs. The advantage of using Clock is that
// tests can substitute a different implementation, allowing the test to control
// time precisely, something required for certain types of tests to be possible
// at all, speeds up execution by not needing to sleep, and can dramatically
// reduce the risk of flakes due to tests executing too slowly or quickly.
type Clock interface {
	// Now returns the current time, as in time.Now.
	Now() time.Time
	// NewTimer returns a timer whose notion of the current time is controlled
	// by this Clock. It follows the semantics of time.NewTimer as closely as
	// possible but is adapted to return an interface, so the channel needs to
	// be returned as well.
	NewTimer(d time.Duration) (TimerController, <-chan time.Time)
	// NewTicker returns a ticker whose notion of the current time is controlled
	// by this Clock. It follows the semantics of time.NewTicker as closely as
	// possible but is adapted to return an interface, so the channel needs to
	// be returned as well.
	NewTicker(d time.Duration) (TickerController, <-chan time.Time)
	// AfterFunc returns a ticker whose notion of the current time is controlled
	// by this Clock. When the ticker expires, it will call the provided func.
	// It follows the semantics of time.AfterFunc.
	AfterFunc(d time.Duration, f func()) TimerController
	// Since returns the time elapsed since t.
	// It follows the semantics of time.Since.
	Since(t time.Time) time.Duration
}

// TickerController offers the receivers of a time.Ticker to ensure
// compatibility with standard timers, but allows for the option of substituting
// a standard timer with something else for testing purposes.
type TickerController interface {
	// Reset follows the same semantics as with time.Ticker.Reset.
	Reset(d time.Duration)
	// Stop follows the same semantics as with time.Ticker.Stop.
	Stop()
}

// TimerController offers the receivers of a time.Timer to ensure
// compatibility with standard timers, but allows for the option of substituting
// a standard timer with something else for testing purposes.
type TimerController interface {
	// Reset follows the same semantics as with time.Timer.Reset.
	Reset(d time.Duration) bool
	// Stop follows the same semantics as with time.Timer.Stop.
	Stop() bool
}

// StdClock is a simple implementation of Clock using the relevant funcs in the
// std/time package.
type StdClock struct{}

// Now calls time.Now.
func (StdClock) Now() time.Time {
	return time.Now()
}

// NewTimer calls time.NewTimer. As an interface does not allow for struct
// members and other packages cannot add receivers to another package, the
// channel is also returned because it would be otherwise inaccessible.
func (StdClock) NewTimer(d time.Duration) (TimerController, <-chan time.Time) {
	t := time.NewTimer(d)
	return t, t.C
}

// NewTicker calls time.NewTicker. As an interface does not allow for struct
// members and other packages cannot add receivers to another package, the
// channel is also returned because it would be otherwise inaccessible.
func (StdClock) NewTicker(d time.Duration) (TickerController, <-chan time.Time) {
	t := time.NewTicker(d)
	return t, t.C
}

// AfterFunc calls time.AfterFunc.
func (StdClock) AfterFunc(d time.Duration, f func()) TimerController {
	return time.AfterFunc(d, f)
}

// Since calls time.Since.
func (StdClock) Since(t time.Time) time.Duration {
	return time.Since(t)
}