package cron

import (
	"context"

	"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
	disetcd "git.imall.cloud/openim/open-im-server-deploy/pkg/common/discovery/etcd"
	mcache "git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/cache/mcache"
	redisCache "git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/cache/redis"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database/mgo"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/dbbuild"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/notification"
	"git.imall.cloud/openim/open-im-server-deploy/pkg/rpcli"
	pbconversation "git.imall.cloud/openim/protocol/conversation"
	"git.imall.cloud/openim/protocol/msg"
	"git.imall.cloud/openim/protocol/third"
	"github.com/openimsdk/tools/discovery"
	"github.com/openimsdk/tools/discovery/etcd"
	"github.com/openimsdk/tools/errs"
	"github.com/openimsdk/tools/log"
	"github.com/openimsdk/tools/mcontext"
	"github.com/openimsdk/tools/s3"
	"github.com/openimsdk/tools/s3/cont"
	"github.com/openimsdk/tools/s3/disable"
	"github.com/openimsdk/tools/s3/minio"
	"github.com/openimsdk/tools/utils/runtimeenv"
	"github.com/robfig/cron/v3"
	"google.golang.org/grpc"
)

type Config struct {
	CronTask     config.CronTask
	Share        config.Share
	Discovery    config.Discovery
	Mongo        config.Mongo
	Redis        config.Redis
	Minio        config.Minio
	Third        config.Third
	Notification config.Notification
}

func Start(ctx context.Context, conf *Config, client discovery.SvcDiscoveryRegistry, service grpc.ServiceRegistrar) error {
	log.CInfo(ctx, "CRON-TASK server is initializing", "runTimeEnv", runtimeenv.RuntimeEnvironment(), "chatRecordsClearTime", conf.CronTask.CronExecuteTime, "msgDestructTime", conf.CronTask.RetainChatRecords)
	if conf.CronTask.RetainChatRecords < 1 {
		log.ZInfo(ctx, "disable cron")
		<-ctx.Done()
		return nil
	}
	ctx = mcontext.SetOpUserID(ctx, conf.Share.IMAdminUser.UserIDs[0])

	msgConn, err := client.GetConn(ctx, conf.Discovery.RpcService.Msg)
	if err != nil {
		return err
	}

	thirdConn, err := client.GetConn(ctx, conf.Discovery.RpcService.Third)
	if err != nil {
		return err
	}

	conversationConn, err := client.GetConn(ctx, conf.Discovery.RpcService.Conversation)
	if err != nil {
		return err
	}

	groupConn, err := client.GetConn(ctx, conf.Discovery.RpcService.Group)
	if err != nil {
		return err
	}

	// 初始化数据库连接（用于会议群聊解散）
	dbb := dbbuild.NewBuilder(&conf.Mongo, &conf.Redis)
	mgocli, err := dbb.Mongo(ctx)
	if err != nil {
		return err
	}
	meetingDB, err := mgo.NewMeetingMongo(mgocli.GetDB())
	if err != nil {
		return err
	}

	systemConfigDB, err := mgo.NewSystemConfigMongo(mgocli.GetDB())
	if err != nil {
		return err
	}

	msgDocDB, err := mgo.NewMsgMongo(mgocli.GetDB())
	if err != nil {
		return err
	}

	objectDB, err := mgo.NewS3Mongo(mgocli.GetDB())
	if err != nil {
		return err
	}

	// 初始化S3客户端和缓存（用于删除S3文件）
	rdb, err := dbb.Redis(ctx)
	if err != nil {
		log.ZWarn(ctx, "Redis连接失败，S3文件删除功能可能受限", err)
		rdb = nil
	}
	var s3Client s3.Interface
	var s3Cache cont.S3Cache
	switch enable := conf.Third.Object.Enable; enable {
	case "minio":
		var minioCache minio.Cache
		if rdb == nil {
			mc, err := mgo.NewCacheMgo(mgocli.GetDB())
			if err != nil {
				log.ZWarn(ctx, "Mongo缓存初始化失败，S3文件删除功能可能受限", err)
				s3Client = disable.NewDisable()
				s3Cache = nil
			} else {
				minioCache = mcache.NewMinioCache(mc)
				s3Client, err = minio.NewMinio(ctx, minioCache, *conf.Minio.Build())
				if err != nil {
					log.ZError(ctx, "Minio初始化失败", err)
					return err
				}
				s3Cache = nil // MongoDB缓存模式下，S3Cache为nil
			}
		} else {
			minioCache = redisCache.NewMinioCache(rdb)
			s3Client, err = minio.NewMinio(ctx, minioCache, *conf.Minio.Build())
			if err != nil {
				log.ZError(ctx, "Minio初始化失败", err)
				return err
			}
			s3Cache = redisCache.NewS3Cache(rdb, s3Client)
		}
	case "":
		s3Client = disable.NewDisable()
		s3Cache = nil
	default:
		// 其他S3类型暂不支持，使用disable模式
		log.ZWarn(ctx, "S3类型不支持，使用disable模式", nil, "enable", enable)
		s3Client = disable.NewDisable()
		s3Cache = nil
	}

	var locker Locker
	if conf.Discovery.Enable == config.ETCD {
		cm := disetcd.NewConfigManager(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient(), []string{
			conf.CronTask.GetConfigFileName(),
			conf.Share.GetConfigFileName(),
			conf.Discovery.GetConfigFileName(),
		})
		cm.Watch(ctx)
		locker, err = NewEtcdLocker(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient())
		if err != nil {
			return err
		}
	}

	if locker == nil {
		locker = emptyLocker{}
	}

	// 初始化NotificationSender（用于发送删除消息通知）
	notificationSender := notification.NewNotificationSender(&conf.Notification,
		notification.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) {
			return msg.NewMsgClient(msgConn).SendMsg(ctx, req)
		}),
	)

	srv := &cronServer{
		ctx:                ctx,
		config:             conf,
		cron:               cron.New(),
		msgClient:          msg.NewMsgClient(msgConn),
		conversationClient: pbconversation.NewConversationClient(conversationConn),
		thirdClient:        third.NewThirdClient(thirdConn),
		groupClient:        rpcli.NewGroupClient(groupConn),
		meetingDB:          meetingDB,
		systemConfigDB:     systemConfigDB,
		msgDocDB:           msgDocDB,
		objectDB:           objectDB,
		s3Client:           s3Client,
		s3Cache:            s3Cache,
		notificationSender: notificationSender,
		locker:             locker,
	}

	if err := srv.registerClearS3(); err != nil {
		return err
	}
	if err := srv.registerDeleteMsg(); err != nil {
		return err
	}
	if err := srv.registerClearUserMsg(); err != nil {
		return err
	}
	if err := srv.registerDismissMeetingGroups(); err != nil {
		return err
	}
	if err := srv.registerClearGroupMsgByConfig(); err != nil {
		return err
	}
	if err := srv.registerClearUserMsgByConfig(); err != nil {
		return err
	}
	log.ZDebug(ctx, "start cron task", "CronExecuteTime", conf.CronTask.CronExecuteTime)
	srv.cron.Start()
	log.ZDebug(ctx, "cron task server is running")
	<-ctx.Done()
	log.ZDebug(ctx, "cron task server is shutting down")
	srv.cron.Stop()

	return nil
}

type Locker interface {
	ExecuteWithLock(ctx context.Context, taskName string, task func())
}

type emptyLocker struct{}

func (emptyLocker) ExecuteWithLock(ctx context.Context, taskName string, task func()) {
	task()
}

type cronServer struct {
	ctx                context.Context
	config             *Config
	cron               *cron.Cron
	msgClient          msg.MsgClient
	conversationClient pbconversation.ConversationClient
	thirdClient        third.ThirdClient
	groupClient        *rpcli.GroupClient
	meetingDB          database.Meeting
	systemConfigDB     database.SystemConfig
	msgDocDB           database.Msg
	objectDB           database.ObjectInfo
	s3Client           s3.Interface
	s3Cache            cont.S3Cache
	notificationSender *notification.NotificationSender
	locker             Locker
}

func (c *cronServer) registerClearS3() error {
	if c.config.CronTask.FileExpireTime <= 0 || len(c.config.CronTask.DeleteObjectType) == 0 {
		log.ZInfo(c.ctx, "disable scheduled cleanup of s3", "fileExpireTime", c.config.CronTask.FileExpireTime, "deleteObjectType", c.config.CronTask.DeleteObjectType)
		return nil
	}
	_, err := c.cron.AddFunc(c.config.CronTask.CronExecuteTime, func() {
		c.locker.ExecuteWithLock(c.ctx, "clearS3", c.clearS3)
	})
	return errs.WrapMsg(err, "failed to register clear s3 cron task")
}

func (c *cronServer) registerDeleteMsg() error {
	if c.config.CronTask.RetainChatRecords <= 0 {
		log.ZInfo(c.ctx, "disable scheduled cleanup of chat records", "retainChatRecords", c.config.CronTask.RetainChatRecords)
		return nil
	}
	_, err := c.cron.AddFunc(c.config.CronTask.CronExecuteTime, func() {
		c.locker.ExecuteWithLock(c.ctx, "deleteMsg", c.deleteMsg)
	})
	return errs.WrapMsg(err, "failed to register delete msg cron task")
}

func (c *cronServer) registerClearUserMsg() error {
	_, err := c.cron.AddFunc(c.config.CronTask.CronExecuteTime, func() {
		c.locker.ExecuteWithLock(c.ctx, "clearUserMsg", c.clearUserMsg)
	})
	return errs.WrapMsg(err, "failed to register clear user msg cron task")
}

func (c *cronServer) registerDismissMeetingGroups() error {
	// 每分钟执行一次，检查已结束超过10分钟的会议并解散群聊
	_, err := c.cron.AddFunc("*/1 * * * *", func() {
		c.locker.ExecuteWithLock(c.ctx, "dismissMeetingGroups", c.dismissMeetingGroups)
	})
	return errs.WrapMsg(err, "failed to register dismiss meeting groups cron task")
}

func (c *cronServer) registerClearGroupMsgByConfig() error {
	// 使用配置文件中的执行时间，清理群聊消息（根据系统配置）
	cronExpr := c.config.CronTask.CronExecuteTime
	log.ZInfo(c.ctx, "[CLEAR_MSG] 注册清理群聊消息定时任务", "cron", cronExpr)
	_, err := c.cron.AddFunc(cronExpr, func() {
		c.locker.ExecuteWithLock(c.ctx, "clearGroupMsgByConfig", c.clearGroupMsg)
	})
	if err != nil {
		log.ZError(c.ctx, "[CLEAR_MSG] 注册清理群聊消息定时任务失败", err)
	} else {
		log.ZInfo(c.ctx, "[CLEAR_MSG] 清理群聊消息定时任务注册成功", "cron", cronExpr)
	}
	return errs.WrapMsg(err, "failed to register clear group msg by config cron task")
}

func (c *cronServer) registerClearUserMsgByConfig() error {
	// 使用配置文件中的执行时间，清理个人聊天消息（根据系统配置）
	cronExpr := c.config.CronTask.CronExecuteTime
	log.ZInfo(c.ctx, "[CLEAR_MSG] 注册清理个人聊天消息定时任务", "cron", cronExpr)
	_, err := c.cron.AddFunc(cronExpr, func() {
		c.locker.ExecuteWithLock(c.ctx, "clearUserMsgByConfig", c.clearUserMsg)
	})
	if err != nil {
		log.ZError(c.ctx, "[CLEAR_MSG] 注册清理个人聊天消息定时任务失败", err)
	} else {
		log.ZInfo(c.ctx, "[CLEAR_MSG] 清理个人聊天消息定时任务注册成功", "cron", cronExpr)
	}
	return errs.WrapMsg(err, "failed to register clear user msg by config cron task")
}
