package failover

import (
	"context"
	"net"
	"time"

	"github.com/sagernet/sing-box/adapter"
	E "github.com/sagernet/sing/common/exceptions"
	"github.com/sagernet/sing/common/logger"
	M "github.com/sagernet/sing/common/metadata"
)

type DialStrategy = func(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error)

func cycleStrategy(outbounds []adapter.Outbound, logger logger.ContextLogger, delay time.Duration) DialStrategy {
	return func(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
		for {
			for _, outbound := range outbounds {
				conn, err := outbound.DialContext(ctx, network, destination)
				if err != nil {
					logger.InfoContext(ctx, err)
					if delay > 0 {
						select {
						case <-time.After(delay):
						case <-ctx.Done():
							return nil, ctx.Err()
						}
					}
					continue
				}
				return conn, nil
			}
		}
	}
}

func sequentialStrategy(outbounds []adapter.Outbound, logger logger.ContextLogger, delay time.Duration) DialStrategy {
	return func(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
		var err error
		for _, outbound := range outbounds {
			var conn net.Conn
			conn, err = outbound.DialContext(ctx, network, destination)
			if err != nil {
				logger.InfoContext(ctx, err)
				if delay > 0 {
					select {
					case <-time.After(delay):
					case <-ctx.Done():
						return nil, ctx.Err()
					}
				}
				continue
			}
			return conn, nil
		}
		return nil, err
	}
}

func CreateStrategy(strategy string, outbounds []adapter.Outbound, logger logger.ContextLogger, delay time.Duration) (DialStrategy, error) {
	switch strategy {
	case "cycle":
		return cycleStrategy(outbounds, logger, delay), nil
	case "sequential", "":
		return sequentialStrategy(outbounds, logger, delay), nil
	default:
		return nil, E.New("strategy not found: ", strategy)
	}
}
