package m7s

import (
	"bytes"
	"context"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"time"

	"gopkg.in/yaml.v3"

	"github.com/quic-go/quic-go"

	gatewayRuntime "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	myip "github.com/husanpao/ip"
	"google.golang.org/grpc"
	"gorm.io/gorm"

	task "github.com/langhuihui/gotask"
	. "m7s.live/v5/pkg"
	"m7s.live/v5/pkg/config"
	"m7s.live/v5/pkg/db"
	"m7s.live/v5/pkg/util"
)

type (
	DefaultYaml    string
	OnExitHandler  func()
	AuthPublisher  = func(*Publisher) *util.Promise
	AuthSubscriber = func(*Subscriber) *util.Promise

	PluginMeta struct {
		Name                string
		Version             string //插件版本
		Type                reflect.Type
		DefaultYaml         DefaultYaml //默认配置
		ServiceDesc         *grpc.ServiceDesc
		RegisterGRPCHandler func(context.Context, *gatewayRuntime.ServeMux, *grpc.ClientConn) error
		NewPuller           PullerFactory
		NewPusher           PusherFactory
		NewRecorder         RecorderFactory
		NewTransformer      TransformerFactory
		NewPullProxy        PullProxyFactory
		NewPushProxy        PushProxyFactory
		OnExit              OnExitHandler
		OnAuthPub           AuthPublisher
		OnAuthSub           AuthSubscriber
	}

	iPlugin interface {
		nothing()
	}

	IPlugin interface {
		task.IJob
		Pull(string, config.Pull, *config.Publish) (*PullJob, error)
		Push(string, config.Push, *config.Subscribe)
		Transform(*Publisher, config.Transform)
		OnPublish(*Publisher)
	}

	IRegisterHandler interface {
		RegisterHandler() map[string]http.HandlerFunc
	}

	IPullerPlugin interface {
		GetPullableList() []string
	}

	ITCPPlugin interface {
		OnTCPConnect(conn *net.TCPConn) task.ITask
	}

	IUDPPlugin interface {
		OnUDPConnect(conn *net.UDPConn) task.ITask
	}

	IQUICPlugin interface {
		OnQUICConnect(quic.Connection) task.ITask
	}
)

var plugins []PluginMeta

func (plugin *PluginMeta) Init(s *Server, userConfig map[string]any) (p *Plugin) {
	instance, ok := reflect.New(plugin.Type).Interface().(IPlugin)
	if !ok {
		panic("plugin must implement IPlugin")
	}
	p = reflect.ValueOf(instance).Elem().FieldByName("Plugin").Addr().Interface().(*Plugin)
	p.handler = instance
	p.Meta = plugin
	p.Server = s
	p.Logger = s.Logger.With("plugin", plugin.Name)
	upperName := strings.ToUpper(plugin.Name)
	if os.Getenv(upperName+"_ENABLE") == "false" {
		p.disable("env")
		p.Warn("disabled by env")
		return
	}
	p.Config.Parse(p.GetCommonConf(), upperName)
	p.Config.Parse(instance, upperName)
	for _, fname := range MergeConfigs {
		if name := strings.ToLower(fname); p.Config.Has(name) {
			p.Config.Get(name).ParseGlobal(s.Config.Get(name))
		}
	}
	if plugin.DefaultYaml != "" {
		var defaultConf map[string]any
		if err := yaml.Unmarshal([]byte(plugin.DefaultYaml), &defaultConf); err != nil {
			p.Error("parsing default config", "error", err)
		} else {
			p.Config.ParseDefaultYaml(defaultConf)
		}
	}
	p.Config.ParseUserFile(userConfig)
	p.SetDescription("version", plugin.Version)
	if userConfig != nil {
		p.SetDescription("userConfig", userConfig)
	}
	finalConfig, _ := yaml.Marshal(p.Config.GetMap())
	p.Logger.Handler().(*MultiLogHandler).SetLevel(ParseLevel(p.config.LogLevel))
	p.Debug("config", "detail", string(finalConfig))
	if userConfig["enable"] == false || (s.DisableAll && userConfig["enable"] != true) {
		p.disable("config")
		return
	}
	p.Info("init", "version", plugin.Version)
	var err error
	if p.config.DSN == s.GetCommonConf().DSN {
		p.DB = s.DB
	} else if p.config.DSN != "" {
		if factory, ok := db.Factory[p.config.DBType]; ok {
			p.DB, err = gorm.Open(factory(p.config.DSN), &gorm.Config{})
			if err != nil {
				s.Error("failed to connect database", "error", err, "dsn", s.config.DSN, "type", s.config.DBType)
				p.disable(fmt.Sprintf("database %v", err))
				return
			}
		}
	}
	if p.DB != nil && p.Meta.NewRecorder != nil {
		if err = p.DB.AutoMigrate(&RecordStream{}); err != nil {
			p.disable(fmt.Sprintf("auto migrate record stream failed %v", err))
			return
		}
		if err = p.DB.AutoMigrate(&EventRecordStream{}); err != nil {
			p.disable(fmt.Sprintf("auto migrate event record stream failed %v", err))
			return
		}
	}
	if err = s.AddTask(instance).WaitStarted(); err != nil {
		p.disable(instance.StopReason().Error())
		return
	}
	if err = p.listen(); err != nil {
		p.Stop(err)
		p.disable(err.Error())
		return
	}
	if p.Meta.ServiceDesc != nil && s.grpcServer != nil {
		s.grpcServer.RegisterService(p.Meta.ServiceDesc, p.handler)
		if p.Meta.RegisterGRPCHandler != nil {
			if err = p.Meta.RegisterGRPCHandler(p.Context, s.config.HTTP.GetGRPCMux(), s.grpcClientConn); err != nil {
				p.Stop(err)
				p.disable(fmt.Sprintf("grpc %v", err))
				return
			} else {
				p.Info("grpc handler registered")
			}
		}
	}
	if p.config.Hook != nil {
		if hook, ok := p.config.Hook[config.HookOnServerKeepAlive]; ok && hook.Interval > 0 {
			p.AddTask(&ServerKeepAliveTask{plugin: p})
		}
	}
	var handlers map[string]http.HandlerFunc
	if v, ok := instance.(IRegisterHandler); ok {
		handlers = v.RegisterHandler()
	}
	p.registerHandler(handlers)
	p.OnDispose(func() {
		s.Plugins.Remove(p)
	})
	s.Plugins.Add(p)
	return
}

// InstallPlugin 安装插件
func InstallPlugin[C iPlugin](meta PluginMeta) error {
	var c *C
	meta.Type = reflect.TypeOf(c).Elem()
	if meta.Name == "" {
		meta.Name = strings.TrimSuffix(meta.Type.Name(), "Plugin")
	}
	_, pluginFilePath, _, _ := runtime.Caller(1)
	configDir := filepath.Dir(pluginFilePath)
	if meta.Version == "" {
		if _, after, found := strings.Cut(configDir, "@"); found {
			meta.Version = after
		} else {
			meta.Version = "dev"
		}
	}
	plugins = append(plugins, meta)
	return nil
}

var _ IPlugin = (*Plugin)(nil)

type Plugin struct {
	task.Work
	config.Config
	Disabled           bool
	Meta               *PluginMeta
	PushAddr, PlayAddr []string
	config             config.Common
	handler            IPlugin
	Server             *Server
	DB                 *gorm.DB
}

func (Plugin) nothing() {

}

func (p *Plugin) GetKey() string {
	return p.Meta.Name
}

func (p *Plugin) GetGlobalCommonConf() *config.Common {
	return p.Server.GetCommonConf()
}

func (p *Plugin) GetCommonConf() *config.Common {
	return &p.config
}

func (p *Plugin) GetHandler() IPlugin {
	return p.handler
}

func (p *Plugin) GetPublicIP(netcardIP string) string {
	if p.config.PublicIP != "" {
		return p.config.PublicIP
	}
	if publicIP := util.GetPublicIP(netcardIP); publicIP != netcardIP {
		return publicIP
	}
	localIp := myip.InternalIPv4()
	if publicIP := util.GetPublicIP(localIp); publicIP != localIp {
		return publicIP
	}
	return localIp
}

func (p *Plugin) disable(reason string) {
	p.Disabled = true
	p.SetDescription("disableReason", reason)
	p.Warn("plugin disabled")
	p.Server.disabledPlugins = append(p.Server.disabledPlugins, p)
}

func (p *Plugin) listen() (err error) {
	httpConf := &p.config.HTTP

	if httpConf.ListenAddrTLS != "" && (httpConf.ListenAddrTLS != p.Server.config.HTTP.ListenAddrTLS) {
		p.SetDescription("httpTLS", strings.TrimPrefix(httpConf.ListenAddrTLS, ":"))
		p.AddDependTask(CreateHTTPSWork(httpConf, p.Logger))
	}

	if httpConf.ListenAddr != "" && (httpConf.ListenAddr != p.Server.config.HTTP.ListenAddr) {
		p.SetDescription("http", strings.TrimPrefix(httpConf.ListenAddr, ":"))
		p.AddDependTask(CreateHTTPWork(httpConf, p.Logger))
	}

	if tcphandler, ok := p.handler.(ITCPPlugin); ok {
		tcpConf := &p.config.TCP
		if tcpConf.ListenAddr != "" {
			if tcpConf.AutoListen {
				if err = p.AddTask(tcpConf.CreateTCPWork(p.Logger, tcphandler.OnTCPConnect)).WaitStarted(); err != nil {
					return
				}
			}
			p.SetDescription("tcp", strings.TrimPrefix(tcpConf.ListenAddr, ":"))
		}
		if tcpConf.ListenAddrTLS != "" {
			if tcpConf.AutoListen {
				if err = p.AddTask(tcpConf.CreateTCPTLSWork(p.Logger, tcphandler.OnTCPConnect)).WaitStarted(); err != nil {
					return
				}
			}
			p.SetDescription("tcpTLS", strings.TrimPrefix(tcpConf.ListenAddrTLS, ":"))
		}
	}

	if udpHandler, ok := p.handler.(IUDPPlugin); ok {
		udpConf := &p.config.UDP
		if udpConf.ListenAddr != "" {
			if udpConf.AutoListen {
				if err = p.AddTask(udpConf.CreateUDPWork(p.Logger, udpHandler.OnUDPConnect)).WaitStarted(); err != nil {
					return
				}
			}
			p.SetDescription("udp", strings.TrimPrefix(udpConf.ListenAddr, ":"))
		}
	}

	if quicHandler, ok := p.handler.(IQUICPlugin); ok {
		quicConf := &p.config.Quic
		if quicConf.ListenAddr != "" {
			if quicConf.AutoListen {
				if err = p.AddTask(quicConf.CreateQUICWork(p.Logger, quicHandler.OnQUICConnect)).WaitStarted(); err != nil {
					return
				}
			}
			p.SetDescription("quic", strings.TrimPrefix(quicConf.ListenAddr, ":"))
		}
	}
	return
}

type WebHookQueueTask struct {
	task.Work
}

var webHookQueueTask WebHookQueueTask

type WebHookTask struct {
	task.Task
	plugin   *Plugin
	hookType config.HookType
	conf     config.Webhook
	data     any
	jsonData []byte
	alarm    AlarmInfo
}

func (t *WebHookTask) Start() error {
	if t.conf.URL == "" {
		return task.ErrTaskComplete
	}

	// 处理AlarmInfo数据
	if t.data != nil {
		// 获取主机名和IP地址
		hostname, err := os.Hostname()
		if err != nil {
			hostname = "unknown"
		}

		// 获取本机IP地址
		var ipAddr string
		addrs, err := net.InterfaceAddrs()
		if err == nil {
			for _, addr := range addrs {
				if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
					if ipnet.IP.To4() != nil {
						ipAddr = ipnet.IP.String()
						break
					}
				}
			}
		}
		if ipAddr == "" {
			ipAddr = "unknown"
		}

		// 直接使用t.data作为AlarmInfo
		alarmInfo, ok := t.data.(AlarmInfo)
		if !ok {
			return fmt.Errorf("data is not of type AlarmInfo")
		}

		// 更新服务器信息
		if alarmInfo.ServerInfo == "" {
			alarmInfo.ServerInfo = fmt.Sprintf("%s (%s)", hostname, ipAddr)
		}

		// 确保时间戳已设置
		if alarmInfo.CreatedAt.IsZero() {
			alarmInfo.CreatedAt = time.Now()
		}
		if alarmInfo.UpdatedAt.IsZero() {
			alarmInfo.UpdatedAt = time.Now()
		}

		// 将AlarmInfo序列化为JSON
		jsonData, err := json.Marshal(alarmInfo)
		if err != nil {
			return fmt.Errorf("marshal AlarmInfo to json: %w", err)
		}

		t.jsonData = jsonData
		t.alarm = alarmInfo
	}

	t.SetRetry(t.conf.RetryTimes, t.conf.RetryInterval)
	return nil
}

func (t *WebHookTask) Go() error {
	// 检查是否需要保存告警到数据库
	var dbID uint
	if t.conf.SaveAlarm && t.plugin.DB != nil {
		// 默认 IsSent 为 false
		t.alarm.IsSent = false
		if err := t.plugin.DB.Create(&t.alarm).Error; err != nil {
			t.plugin.Error("保存告警到数据库失败", "error", err)
		} else {
			dbID = t.alarm.ID
			t.plugin.Info(""+
				"", "id", dbID)
		}
	}

	req, err := http.NewRequest(t.conf.Method, t.conf.URL, bytes.NewBuffer(t.jsonData))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json")
	for k, v := range t.conf.Headers {
		req.Header.Set(k, v)
	}

	client := &http.Client{
		Timeout: time.Duration(t.conf.TimeoutSeconds) * time.Second,
	}

	resp, err := client.Do(req)
	if err != nil {
		t.plugin.Error("webhook请求失败", "error", err)
		return err
	}
	defer resp.Body.Close()

	// 如果发送成功且已保存到数据库，则更新IsSent字段为true
	if resp.StatusCode >= 200 && resp.StatusCode < 300 && t.conf.SaveAlarm && t.plugin.DB != nil && dbID > 0 {
		t.alarm.IsSent = true
		if err := t.plugin.DB.Model(&AlarmInfo{}).Where("id = ?", dbID).Update("is_sent", true).Error; err != nil {
			t.plugin.Error("更新告警发送状态失败", "error", err)
		} else {
			t.plugin.Info("告警发送状态已更新", "id", dbID, "is_sent", true)
		}
		return task.ErrTaskComplete
	}

	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		return task.ErrTaskComplete
	}

	err = fmt.Errorf("webhook请求失败，状态码：%d", resp.StatusCode)
	t.plugin.Error("webhook响应错误", "状态码", resp.StatusCode)
	return err
}

func (p *Plugin) SendWebhook(conf config.Webhook, data any) *task.Task {
	webhookTask := &WebHookTask{
		plugin: p,
		conf:   conf,
		data:   data,
	}
	return webHookQueueTask.AddTask(webhookTask)
}

// TODO: use alias stream
func (p *Plugin) OnPublish(pub *Publisher) {
	onPublish := p.config.OnPub
	if p.Meta.NewPusher != nil {
		for r, pushConf := range onPublish.Push {
			if pushConf.URL = r.Replace(pub.StreamPath, pushConf.URL); pushConf.URL != "" {
				p.Push(pub.StreamPath, pushConf, nil)
			}
		}
	}
	if p.Meta.NewRecorder != nil {
		for r, recConf := range onPublish.Record {
			if recConf.FilePath = r.Replace(pub.StreamPath, recConf.FilePath); recConf.FilePath != "" {
				p.Record(pub, recConf, nil)
			}
		}
	}
	var owner = pub.Value(Owner)
	var isTransformer bool
	if owner != nil {
		_, isTransformer = owner.(ITransformer)
	}
	if p.Meta.NewTransformer != nil && !isTransformer {
		for r, tranConf := range onPublish.Transform {
			if group := r.FindStringSubmatch(pub.StreamPath); group != nil {
				for j, to := range tranConf.Output {
					for i, g := range group {
						to.Target = strings.ReplaceAll(to.Target, fmt.Sprintf("$%d", i), g)
					}
					targetUrl, err := url.Parse(to.Target)
					if err == nil {
						to.StreamPath = strings.TrimPrefix(targetUrl.Path, "/")
					}
					tranConf.Output[j] = to
				}
				p.Transform(pub, tranConf)
			}
		}
	}
}

func (p *Plugin) auth(streamPath string, key string, secret string, expire string) (err error) {
	if unixTime, err := strconv.ParseInt(expire, 16, 64); err != nil || time.Now().Unix() > unixTime {
		return fmt.Errorf("auth failed expired")
	}
	if len(secret) != 32 {
		return fmt.Errorf("auth failed secret length must be 32")
	}
	trueSecret := md5.Sum([]byte(key + streamPath + expire))
	if secret == hex.EncodeToString(trueSecret[:]) {
		return nil
	}
	return fmt.Errorf("auth failed invalid secret")
}

func (p *Plugin) OnSubscribe(streamPath string, args url.Values) {
	//	var avoidTrans bool
	//AVOID:
	//	for trans := range server.Transforms.Range {
	//		for _, output := range trans.Config.Output {
	//			if output.StreamPath == s.StreamPath {
	//				avoidTrans = true
	//				break AVOID
	//			}
	//		}
	//	}
	for reg, conf := range p.config.OnSub.Pull {
		if p.Meta.NewPuller != nil && reg.MatchString(streamPath) {
			conf.Args = config.HTTPValues(args)
			conf.URL = reg.Replace(streamPath, conf.URL)
			if job, err := p.handler.Pull(streamPath, conf, nil); err == nil {
				if w, ok := p.Server.Waiting.Get(streamPath); ok {
					job.Progress = &w.Progress
				}
			}
		}
	}

	//if !avoidTrans {
	//	for reg, conf := range plugin.GetCommonConf().OnSub.Transform {
	//		if plugin.Meta.Transformer != nil {
	//			if reg.MatchString(s.StreamPath) {
	//				if group := reg.FindStringSubmatch(s.StreamPath); group != nil {
	//					for j, c := range conf.Output {
	//						for i, value := range group {
	//							c.Target = strings.Replace(c.Target, fmt.Sprintf("$%d", i), value, -1)
	//						}
	//						conf.Output[j] = c
	//					}
	//				}
	//				plugin.handler.Transform(s.StreamPath, conf)
	//			}
	//		}
	//	}
	//}
}

func (p *Plugin) PublishWithConfig(ctx context.Context, streamPath string, conf config.Publish) (publisher *Publisher, err error) {
	publisher = &Publisher{Publish: conf}
	publisher.Type = conf.PubType
	publisher.ID = task.GetNextTaskID()
	publisher.Plugin = p
	if conf.PublishTimeout > 0 {
		publisher.TimeoutTimer = time.NewTimer(conf.PublishTimeout)
	} else {
		publisher.TimeoutTimer = time.NewTimer(time.Hour * 24 * 365)
	}
	publisher.Logger = p.Logger.With("streamPath", streamPath, "pId", publisher.ID)
	publisher.Init(streamPath, &publisher.Publish)
	if p.config.EnableAuth && publisher.Type == PublishTypeServer {
		onAuthPub := p.Meta.OnAuthPub
		if onAuthPub == nil {
			onAuthPub = p.Server.Meta.OnAuthPub
		}
		if onAuthPub != nil {
			if err = onAuthPub(publisher).Await(); err != nil {
				p.Warn("auth failed", "error", err)
				return
			}
		} else if conf.Key != "" {
			if err = p.auth(publisher.StreamPath, conf.Key, publisher.Args.Get("secret"), publisher.Args.Get("expire")); err != nil {
				p.Warn("auth failed", "error", err)
				return
			}
		}
	}
	for {
		err = p.Server.Streams.Add(publisher, ctx).WaitStarted()
		if err == nil {
			if sender, webhook := p.getHookSender(config.HookOnPublishEnd); sender != nil {
				publisher.OnDispose(func() {
					alarmInfo := AlarmInfo{
						AlarmName:  string(config.HookOnPublishEnd),
						AlarmDesc:  publisher.StopReason().Error(),
						AlarmType:  config.AlarmPublishOffline,
						StreamPath: publisher.StreamPath,
					}
					sender(webhook, alarmInfo)
				})
			}
			if sender, webhook := p.getHookSender(config.HookOnPublishStart); sender != nil {
				alarmInfo := AlarmInfo{
					AlarmName:  string(config.HookOnPublishStart),
					AlarmType:  config.AlarmPublishRecover,
					StreamPath: publisher.StreamPath,
				}
				sender(webhook, alarmInfo)
			}
			return
		} else if oldStream := new(task.ExistTaskError); errors.As(err, oldStream) {
			if conf.KickExist {
				publisher.takeOver(oldStream.Task.(*Publisher))
				oldStream.Task.WaitStopped()
			} else {
				return nil, ErrStreamExist
			}
		} else {
			return
		}
	}
}

func (p *Plugin) Publish(ctx context.Context, streamPath string) (publisher *Publisher, err error) {
	return p.PublishWithConfig(ctx, streamPath, p.config.Publish)
}

func (p *Plugin) SubscribeWithConfig(ctx context.Context, streamPath string, conf config.Subscribe) (subscriber *Subscriber, err error) {
	subscriber = createSubscriber(p, streamPath, conf)
	if p.config.EnableAuth && subscriber.Type == SubscribeTypeServer {
		onAuthSub := p.Meta.OnAuthSub
		if onAuthSub == nil {
			onAuthSub = p.Server.Meta.OnAuthSub
		}
		if onAuthSub != nil {
			if err = onAuthSub(subscriber).Await(); err != nil {
				p.Warn("auth failed", "error", err)
				return
			}
		} else if conf.Key != "" {
			if err = p.auth(subscriber.StreamPath, conf.Key, subscriber.Args.Get("secret"), subscriber.Args.Get("expire")); err != nil {
				p.Warn("auth failed", "error", err)
				return
			}
		}
	}
	err = p.Server.Streams.AddTask(subscriber, ctx).WaitStarted()
	if err == nil {
		select {
		case <-subscriber.waitPublishDone:
			waitAudio := conf.WaitTrack == "all" || strings.Contains(conf.WaitTrack, "audio")
			waitVideo := conf.WaitTrack == "all" || strings.Contains(conf.WaitTrack, "video")
			err = subscriber.Publisher.WaitTrack(waitAudio, waitVideo)
		case <-subscriber.Done():
			err = subscriber.StopReason()
		}
	}
	if err == nil {
		if sender, webhook := p.getHookSender(config.HookOnSubscribeEnd); sender != nil {
			subscriber.OnDispose(func() {
				alarmInfo := AlarmInfo{
					AlarmName:  string(config.HookOnSubscribeEnd),
					AlarmDesc:  subscriber.StopReason().Error(),
					AlarmType:  config.AlarmSubscribeOffline,
					StreamPath: subscriber.StreamPath,
				}
				sender(webhook, alarmInfo)
			})
		}
		if sender, webhook := p.getHookSender(config.HookOnSubscribeStart); sender != nil {
			alarmInfo := AlarmInfo{
				AlarmName:  string(config.HookOnSubscribeStart),
				AlarmType:  config.AlarmSubscribeRecover,
				StreamPath: subscriber.StreamPath,
			}
			sender(webhook, alarmInfo)
		}
	}
	return
}

func (p *Plugin) Subscribe(ctx context.Context, streamPath string) (subscriber *Subscriber, err error) {
	return p.SubscribeWithConfig(ctx, streamPath, p.config.Subscribe)
}

func (p *Plugin) Pull(streamPath string, conf config.Pull, pubConf *config.Publish) (job *PullJob, err error) {
	puller := p.Meta.NewPuller(conf)
	if puller == nil {
		return nil, ErrNotFound
	}
	job = puller.GetPullJob()
	job.Init(puller, p, streamPath, conf, pubConf)
	return
}

func (p *Plugin) Push(streamPath string, conf config.Push, subConf *config.Subscribe) {
	pusher := p.Meta.NewPusher()
	pusher.GetPushJob().Init(pusher, p, streamPath, conf, subConf)
}

func (p *Plugin) Record(pub *Publisher, conf config.Record, subConf *config.Subscribe) *RecordJob {
	recorder := p.Meta.NewRecorder(conf)
	job := recorder.GetRecordJob().Init(recorder, p, pub.StreamPath, conf, subConf)
	pub.Using(job)
	return job
}

func (p *Plugin) Transform(pub *Publisher, conf config.Transform) {
	transformer := p.Meta.NewTransformer()
	pub.Using(transformer.GetTransformJob().Init(transformer, p, pub, conf))
}

func (p *Plugin) registerHandler(handlers map[string]http.HandlerFunc) {
	t := reflect.TypeOf(p.handler)
	v := reflect.ValueOf(p.handler)
	// 注册http响应
	for i, j := 0, t.NumMethod(); i < j; i++ {
		name := t.Method(i).Name
		if name == "ServeHTTP" {
			continue
		}
		switch handler := v.Method(i).Interface().(type) {
		case func(http.ResponseWriter, *http.Request):
			patten := strings.ToLower(strings.ReplaceAll(name, "_", "/"))
			p.handle(patten, http.HandlerFunc(handler))
		}
	}
	for patten, handler := range handlers {
		p.handle(patten, handler)
	}
	if p.config.EnableAuth && p.Server.ServerConfig.Admin.EnableLogin {
		p.handle("/api/secret/{type}/{streamPath...}", http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
			authHeader := r.Header.Get("Authorization")
			if authHeader == "" {
				http.Error(rw, "missing authorization header", http.StatusUnauthorized)
				return
			}

			tokenString := strings.TrimPrefix(authHeader, "Bearer ")
			_, err := p.Server.ValidateToken(tokenString)
			if err != nil {
				http.Error(rw, "invalid token", http.StatusUnauthorized)
				return
			}
			streamPath := r.PathValue("streamPath")
			t := r.PathValue("type")
			expire := r.URL.Query().Get("expire")
			switch t {
			case "publish":
				secret := md5.Sum([]byte(p.config.Publish.Key + streamPath + expire))
				rw.Write([]byte(hex.EncodeToString(secret[:])))
			case "subscribe":
				secret := md5.Sum([]byte(p.config.Subscribe.Key + streamPath + expire))
				rw.Write([]byte(hex.EncodeToString(secret[:])))
			}
		}))
	}
	if rootHandler, ok := p.handler.(http.Handler); ok {
		p.handle("/", rootHandler)
	}
}

func (p *Plugin) logHandler(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
		p.Debug("visit", "path", r.URL.String(), "remote", r.RemoteAddr)
		name := strings.ToLower(p.Meta.Name)
		r.URL.Path = strings.TrimPrefix(r.URL.Path, "/"+name)
		handler.ServeHTTP(rw, r)
	})
}

func (p *Plugin) handle(pattern string, handler http.Handler) {
	if p == nil {
		return
	}
	last := pattern == "/"
	if !strings.HasPrefix(pattern, "/") {
		pattern = "/" + pattern
	}
	handler = p.logHandler(handler)
	p.config.HTTP.Handle(pattern, handler, last)
	if p.Server != p.handler {
		pattern = "/" + strings.ToLower(p.Meta.Name) + pattern
		p.Debug("http handle added to Server", "pattern", pattern)
		p.Server.config.HTTP.Handle(pattern, handler, last)
	}
	p.Server.apiList = append(p.Server.apiList, pattern)
}

func (p *Plugin) getHookSender(hookType config.HookType) (sender func(webhook config.Webhook, data any) *task.Task, conf config.Webhook) {
	if p.config.Hook != nil {
		if _, ok := p.config.Hook[hookType]; ok {
			sender = p.SendWebhook
			conf = p.config.Hook[hookType]
		} else if _, ok := p.config.Hook[config.HookDefault]; ok {
			sender = p.SendWebhook
			conf = p.config.Hook[config.HookDefault]
		} else if p.Server.config.Hook != nil {
			if _, ok := p.Server.config.Hook[hookType]; ok {
				conf = p.config.Hook[hookType]
				sender = p.Server.SendWebhook
			} else if _, ok := p.Server.config.Hook[config.HookDefault]; ok {
				sender = p.Server.SendWebhook
				conf = p.config.Hook[config.HookDefault]
			}
		}
	}
	return
}

type ServerKeepAliveTask struct {
	task.TickTask
	plugin *Plugin
}

func (t *ServerKeepAliveTask) GetTickInterval() time.Duration {
	return time.Duration(t.plugin.config.Hook[config.HookOnServerKeepAlive].Interval) * time.Second
}

func (t *ServerKeepAliveTask) Tick(now any) {
	sender, webhook := t.plugin.getHookSender(config.HookOnServerKeepAlive)
	if sender == nil {
		return
	}
	//s := t.plugin.Server
	alarmInfo := AlarmInfo{
		AlarmName:  string(config.HookOnServerKeepAlive),
		AlarmType:  config.AlarmKeepAliveOnline,
		StreamPath: "",
	}
	sender(webhook, alarmInfo)
	//webhookData := map[string]interface{}{
	//	"event":           config.HookOnServerKeepAlive,
	//	"timestamp":       time.Now().Unix(),
	//	"streams":         s.Streams.Length,
	//	"subscribers":     s.Subscribers.Length,
	//	"publisherCount":  s.Streams.Length,
	//	"subscriberCount": s.Subscribers.Length,
	//	"uptime":          time.Since(s.StartTime).Seconds(),
	//}
	//sender(webhook, webhookData)
}
