package node

import (
	"context"
	"sync"

	"github.com/sagernet/sing-box/adapter"
	boxService "github.com/sagernet/sing-box/adapter/service"
	C "github.com/sagernet/sing-box/constant"
	"github.com/sagernet/sing-box/log"
	"github.com/sagernet/sing-box/option"
	CM "github.com/sagernet/sing-box/service/manager/constant"
	"github.com/sagernet/sing-box/service/node/constant"
	"github.com/sagernet/sing-box/service/node/inbound"
	"github.com/sagernet/sing-box/service/node/limiter"
	E "github.com/sagernet/sing/common/exceptions"
	"github.com/sagernet/sing/service"
)

func RegisterService(registry *boxService.Registry) {
	boxService.Register[option.NodeServiceOptions](registry, C.TypeNode, NewService)
}

type Service struct {
	boxService.Adapter
	ctx               context.Context
	logger            log.ContextLogger
	inboundManagers   map[string]constant.InboundManager
	bandwidthManager  constant.BandwidthLimiterManager
	connectionManager constant.ConnectionLimiterManager
	trafficManager    constant.TrafficLimiterManager
	rateManager       constant.RateLimiterManager
	options           option.NodeServiceOptions

	mtx sync.Mutex
}

func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.NodeServiceOptions) (adapter.Service, error) {
	return &Service{
		Adapter: boxService.NewAdapter(C.TypeManager, tag),
		ctx:     ctx,
		logger:  logger,
		options: options,
	}, nil
}

func (s *Service) Start(stage adapter.StartStage) error {
	if stage != adapter.StartStateStart {
		return nil
	}
	boxManager := service.FromContext[adapter.ServiceManager](s.ctx)
	serviceManager, ok := boxManager.Get(s.options.Manager)
	if !ok {
		return E.New("manager ", s.options.Manager, " not found")
	}
	nodeManager, ok := serviceManager.(CM.NodeManager)
	if !ok {
		return E.New("invalid ", s.options.Manager, " manager")
	}
	inboundManager := service.FromContext[adapter.InboundManager](s.ctx)
	outboundManager := service.FromContext[adapter.OutboundManager](s.ctx)
	s.inboundManagers = map[string]constant.InboundManager{
		"hysteria":  inbound.NewHysteriaManager(),
		"hysteria2": inbound.NewHysteria2Manager(),
		"mtproxy":   inbound.NewMTProxyManager(),
		"trojan":    inbound.NewTrojanManager(),
		"tuic":      inbound.NewTUICManager(),
		"vless":     inbound.NewVLESSManager(),
		"vmess":     inbound.NewVMessManager(),
	}
	s.connectionManager = limiter.NewConnectionLimiterManager(s.ctx, nodeManager, s.logger)
	s.bandwidthManager = limiter.NewBandwidthLimiterManager(s.ctx, nodeManager, s.logger)
	s.trafficManager = limiter.NewTrafficLimiterManager(s.ctx, nodeManager, s.logger)
	s.rateManager = limiter.NewRateLimiterManager(s.ctx, nodeManager, s.logger)
	for _, tag := range s.options.Inbounds {
		inbound, ok := inboundManager.Get(tag)
		if !ok {
			return E.New("inbound ", tag, " not found")
		}
		inboundManager, ok := s.inboundManagers[inbound.Type()]
		if !ok {
			return E.New("inbound manager for ", tag, " not found")
		}
		err := inboundManager.AddUserManager(inbound)
		if err != nil {
			return err
		}
	}
	for _, limiter := range s.options.ConnectionLimiters {
		outbound, ok := outboundManager.Outbound(limiter)
		if !ok {
			return E.New("outbound ", limiter, " not found")
		}
		err := s.connectionManager.AddConnectionLimiterStrategyManager(outbound)
		if err != nil {
			return err
		}
	}
	for _, limiter := range s.options.BandwidthLimiters {
		outbound, ok := outboundManager.Outbound(limiter)
		if !ok {
			return E.New("outbound " + limiter + " not found")
		}
		err := s.bandwidthManager.AddBandwidthLimiterStrategyManager(outbound)
		if err != nil {
			return err
		}
	}
	for _, limiter := range s.options.TrafficLimiters {
		outbound, ok := outboundManager.Outbound(limiter)
		if !ok {
			return E.New("outbound ", limiter, " not found")
		}
		err := s.trafficManager.AddTrafficLimiterStrategyManager(outbound)
		if err != nil {
			return err
		}
	}
	for _, limiter := range s.options.RateLimiters {
		outbound, ok := outboundManager.Outbound(limiter)
		if !ok {
			return E.New("outbound ", limiter, " not found")
		}
		err := s.rateManager.AddRateLimiterStrategyManager(outbound)
		if err != nil {
			return err
		}
	}
	return nodeManager.AddNode(s.options.UUID, s)
}

func (s *Service) UpdateUser(user CM.User) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	manager, ok := s.inboundManagers[user.Type]
	if !ok {
		return
	}
	userManager, ok := manager.GetUserManager(user.Inbound)
	if !ok {
		return
	}
	userManager.UpdateUser(user)
}

func (s *Service) UpdateUsers(users []CM.User) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	typedUsers := make(map[string][]CM.User)
	for _, user := range users {
		u, ok := typedUsers[user.Type]
		if !ok {
			typedUsers[user.Type] = make([]CM.User, 0)
		}
		typedUsers[user.Type] = append(u, user)
	}
	for type_, users := range typedUsers {
		manager, ok := s.inboundManagers[type_]
		if !ok {
			continue
		}
		for _, user := range users {
			userManager, ok := manager.GetUserManager(user.Inbound)
			if !ok {
				continue
			}
			userManager.UpdateUsers(users)
		}
	}
}

func (s *Service) DeleteUser(user CM.User) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	manager, ok := s.inboundManagers[user.Type]
	if !ok {
		return
	}
	userManager, ok := manager.GetUserManager(user.Inbound)
	if !ok {
		return
	}
	userManager.DeleteUser(user.Username)
}

func (s *Service) UpdateConnectionLimiter(limiter CM.ConnectionLimiter) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	manager, ok := s.connectionManager.GetConnectionLimiterStrategyManager(limiter.Outbound)
	if !ok {
		return
	}
	manager.UpdateConnectionLimiter(limiter)
}

func (s *Service) UpdateConnectionLimiters(limiters []CM.ConnectionLimiter) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	for _, limiter := range limiters {
		manager, ok := s.connectionManager.GetConnectionLimiterStrategyManager(limiter.Outbound)
		if !ok {
			continue
		}
		manager.UpdateConnectionLimiters(limiters)
	}
}

func (s *Service) DeleteConnectionLimiter(limiter CM.ConnectionLimiter) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	manager, ok := s.connectionManager.GetConnectionLimiterStrategyManager(limiter.Outbound)
	if !ok {
		return
	}
	manager.DeleteConnectionLimiter(limiter.Username)
}

func (s *Service) UpdateBandwidthLimiter(limiter CM.BandwidthLimiter) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	manager, ok := s.bandwidthManager.GetBandwidthLimiterStrategyManager(limiter.Outbound)
	if !ok {
		return
	}
	manager.UpdateBandwidthLimiter(limiter)
}

func (s *Service) UpdateBandwidthLimiters(limiters []CM.BandwidthLimiter) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	for _, limiter := range limiters {
		manager, ok := s.bandwidthManager.GetBandwidthLimiterStrategyManager(limiter.Outbound)
		if !ok {
			continue
		}
		manager.UpdateBandwidthLimiters(limiters)
	}
}

func (s *Service) DeleteBandwidthLimiter(limiter CM.BandwidthLimiter) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	manager, ok := s.bandwidthManager.GetBandwidthLimiterStrategyManager(limiter.Outbound)
	if !ok {
		return
	}
	manager.DeleteBandwidthLimiter(limiter.Username)
}

func (s *Service) UpdateTrafficLimiter(limiter CM.TrafficLimiter) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	manager, ok := s.trafficManager.GetTrafficLimiterStrategyManager(limiter.Outbound)
	if !ok {
		return
	}
	manager.UpdateTrafficLimiter(limiter)
}

func (s *Service) UpdateTrafficLimiters(limiters []CM.TrafficLimiter) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	for _, limiter := range limiters {
		manager, ok := s.trafficManager.GetTrafficLimiterStrategyManager(limiter.Outbound)
		if !ok {
			continue
		}
		manager.UpdateTrafficLimiters(limiters)
	}
}

func (s *Service) DeleteTrafficLimiter(limiter CM.TrafficLimiter) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	manager, ok := s.trafficManager.GetTrafficLimiterStrategyManager(limiter.Outbound)
	if !ok {
		return
	}
	manager.DeleteTrafficLimiter(limiter.Username)
}

func (s *Service) UpdateRateLimiter(limiter CM.RateLimiter) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	manager, ok := s.rateManager.GetRateLimiterStrategyManager(limiter.Outbound)
	if !ok {
		return
	}
	manager.UpdateRateLimiter(limiter)
}

func (s *Service) UpdateRateLimiters(limiters []CM.RateLimiter) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	for _, limiter := range limiters {
		manager, ok := s.rateManager.GetRateLimiterStrategyManager(limiter.Outbound)
		if !ok {
			continue
		}
		manager.UpdateRateLimiters(limiters)
	}
}

func (s *Service) DeleteRateLimiter(limiter CM.RateLimiter) {
	s.mtx.Lock()
	defer s.mtx.Unlock()
	manager, ok := s.rateManager.GetRateLimiterStrategyManager(limiter.Outbound)
	if !ok {
		return
	}
	manager.DeleteRateLimiter(limiter.Username)
}

func (s *Service) IsLocal() bool {
	return true
}

func (s *Service) IsOnline() bool {
	return true
}

func (s *Service) Close() error {
	return nil
}
