package adapter

import (
	"context"
	"reflect"
	"strings"
	"time"

	"github.com/sagernet/sing-box/log"
	E "github.com/sagernet/sing/common/exceptions"
	F "github.com/sagernet/sing/common/format"
)

type SimpleLifecycle interface {
	Start() error
	Close() error
}

type StartStage uint8

const (
	StartStateInitialize StartStage = iota
	StartStateStart
	StartStatePostStart
	StartStateStarted
)

var ListStartStages = []StartStage{
	StartStateInitialize,
	StartStateStart,
	StartStatePostStart,
	StartStateStarted,
}

func (s StartStage) String() string {
	switch s {
	case StartStateInitialize:
		return "initialize"
	case StartStateStart:
		return "start"
	case StartStatePostStart:
		return "post-start"
	case StartStateStarted:
		return "finish-start"
	default:
		panic("unknown stage")
	}
}

type Lifecycle interface {
	Start(stage StartStage) error
	Close() error
}

type LifecycleService interface {
	Name() string
	Lifecycle
}

func getServiceName(service any) string {
	if named, ok := service.(interface {
		Type() string
		Tag() string
	}); ok {
		tag := named.Tag()
		if tag != "" {
			return named.Type() + "[" + tag + "]"
		}
		return named.Type()
	}
	t := reflect.TypeOf(service)
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	return strings.ToLower(t.Name())
}

func Start(ctx context.Context, logger log.ContextLogger, stage StartStage, services ...Lifecycle) error {
	for _, service := range services {
		err := ctx.Err()
		if err != nil {
			return err
		}
		name := getServiceName(service)
		done := LogElapsed(logger, stage, " ", name)
		err = service.Start(stage)
		done()
		if err != nil {
			return err
		}
	}
	return nil
}

func StartNamed(ctx context.Context, logger log.ContextLogger, stage StartStage, services []LifecycleService) error {
	for _, service := range services {
		err := ctx.Err()
		if err != nil {
			return err
		}
		done := LogElapsed(logger, stage, " ", service.Name())
		err = service.Start(stage)
		done()
		if err != nil {
			return E.Cause(err, stage.String(), " ", service.Name())
		}
	}
	return nil
}

func LogElapsed(logger log.ContextLogger, description ...any) func() {
	prefix := F.ToString(description...)
	startTime := time.Now()
	timer := time.AfterFunc(time.Second, func() {
		logger.Trace(prefix, "...")
	})
	return func() {
		if timer.Stop() {
			return
		}
		logger.Trace(prefix, " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)")
	}
}
