package core

import (
	"github.com/civet148/log"
	"github.com/civet148/sqlca/v2"
	"github.com/go-co-op/gocron"
	"intent-system/pkg/config"
	"intent-system/pkg/dal/dao"
	"intent-system/pkg/dal/models"
	"intent-system/pkg/email"
	"intent-system/pkg/utils"
	"time"
)

const (
	syncNewsAiIntervalDuration     = "30s" //AI新闻文章数据同步时间间隔
	syncNewsSpiderIntervalDuration = "30s" //爬虫新闻文章数据同步时间间隔
	syncQnaAiIntervalDuration      = "30s" //AI Q&A数据同步时间间隔
)

type SchedulerCore struct {
	my              *sqlca.Engine
	pg              *sqlca.Engine
	cfg             *config.Config
	cron            *gocron.Scheduler
	newsDAO         *dao.NewsDAO
	newsSpiderDAO   *dao.NewsSpiderDAO
	wkSpiderNewsDAO *dao.WkSpiderNewsDAO
	wkAigNewsDAO    *dao.WkAigNewsDAO
	wkSpiderQnaDAO  *dao.WkSpiderQnaDAO
	wkAigQnaDAO     *dao.WkAigQnaDAO
	subscriberDAO   *dao.SubscriberDAO
	subDAO          *dao.NewsSubscribeDAO
	dictionaryDAO   *dao.DictionaryDAO
	qaDAO           *dao.QuestionAnswerDAO
}

func NewSchedulerCore(cfg *config.Config, my, pg *sqlca.Engine) *SchedulerCore {
	scheduler := &SchedulerCore{
		my:              my,
		pg:              pg,
		cfg:             cfg,
		cron:            gocron.NewScheduler(time.Local),
		wkAigNewsDAO:    dao.NewWkAigNewsDAO(pg),
		wkSpiderNewsDAO: dao.NewWkSpiderNewsDAO(pg),
		wkAigQnaDAO:     dao.NewWkAigQnaDAO(pg),
		wkSpiderQnaDAO:  dao.NewWkSpiderQnaDAO(pg),
		newsDAO:         dao.NewNewsDAO(my),
		newsSpiderDAO:   dao.NewNewsSpiderDAO(my),
		subscriberDAO:   dao.NewSubscriberDAO(my),
		subDAO:          dao.NewNewsSubscribeDAO(my),
		dictionaryDAO:   dao.NewDictionaryDAO(my),
		qaDAO:           dao.NewQuestionAnswerDAO(my),
	}
	return scheduler.runSchedule()
}

func (m *SchedulerCore) runSchedule() *SchedulerCore {
	_, err := m.cron.Every(syncNewsSpiderIntervalDuration).Do(func() {
		m.runCronTaskSyncNewsSpider()
	})
	if err != nil {
		log.Panic("start crontab task error [%s]", err.Error())
	}
	_, err = m.cron.Every(syncNewsAiIntervalDuration).Do(func() {
		m.runCronTaskSyncNewsAI(models.Language_EN, models.TableNameWkAigNews)
		m.runCronTaskSyncNewsAI(models.Language_CN, models.TableNameWkAigNewsChs)
	})
	if err != nil {
		log.Panic("start crontab task error [%s]", err.Error())
	}
	_, err = m.cron.Every(syncQnaAiIntervalDuration).Do(func() {
		m.runCronTaskSyncQnaAI(models.Language_EN, models.TableNameWkAigQna)
		m.runCronTaskSyncQnaAI(models.Language_CN, models.TableNameWkAigQnaChs)
	})
	if err != nil {
		log.Panic("start crontab task error [%s]", err.Error())
	}
	_, err = m.cron.Cron(m.cfg.SubCron).Do(func() {
		m.runCronTaskSubscribe()
	})
	if err != nil {
		log.Panic("start crontab task error [%s]", err.Error())
	}
	m.cron.StartAsync()
	return m
}

func (m *SchedulerCore) runCronTaskSubscribe() {
	dos, err := m.subscriberDAO.QueryByCondition(map[string]interface{}{
		models.SUBSCRIBER_COLUMN_IS_DELETED: 0,
	})
	if err != nil {
		log.Errorf(err.Error())
		return
	}
	if len(dos) == 0 {
		return //no subscriber
	}
	var newses []*models.NewsSubscribeDO
	newses, err = m.subDAO.QueryByCondition(map[string]interface{}{
		models.NEWS_SUBSCRIBE_COLUMN_IS_PUSHED:  0,
		models.NEWS_SUBSCRIBE_COLUMN_IS_DELETED: 0,
	})
	if err != nil {
		log.Errorf(err.Error())
		return
	}
	for _, news := range newses {
		m.pushNewsToSubscribers(news, dos)
		time.Sleep(30 * time.Second)
	}
}

func (m *SchedulerCore) runCronTaskSyncNewsAI(language models.LanguageType, strTable string) {
	lastId, err := m.newsDAO.QueryMaxSyncId(language)
	if err != nil {
		log.Errorf("query max news id from mysql error [%s]", err.Error())
		return
	}
	var dos []*models.WkAigNewsDO
	dos, err = m.wkAigNewsDAO.QueryLatest(strTable, lastId)
	if err != nil {
		log.Errorf("sync from postgresql database error [%s]", err.Error())
		return
	}
	for _, do := range dos {
		var tags = []string{do.Tag}
		if do.Summary == "" {
			do.Summary = do.SubTitle
		}
		_, err = m.newsDAO.Insert(&models.NewsDO{
			OrgId:       do.Id,
			SpiderId:    do.OrgId,
			Tag:         do.Tag,
			Tags:        tags,
			Category:    do.Category,
			MainTitle:   do.MainTitle,
			SubTitle:    do.SubTitle,
			Summary:     do.Summary,
			Keywords:    do.Keywords,
			SeoKeywords: do.SeoKeywords,
			Url:         do.Url,
			ImageUrl:    do.ImageUrl,
			Content:     do.Content,
			IsHotspot:   do.IsHotspot,
			IsDeleted:   false,
			Language:    language,
			DataTime:    utils.FormatTimestampTZ(do.CreatedTime),
		})
		if err != nil {
			log.Errorf("insert database error [%s]", err.Error())
			return
		}
	}
}

func (m *SchedulerCore) runCronTaskSyncNewsSpider() {
	lastId, err := m.newsSpiderDAO.QueryMaxSyncId()
	if err != nil {
		log.Errorf("query max news id from mysql error [%s]", err.Error())
		return
	}
	var dos []*models.WkSpiderNewsDO
	dos, err = m.wkSpiderNewsDAO.QueryLatest(lastId)
	if err != nil {
		log.Errorf("sync from postgresql database error [%s]", err.Error())
		return
	}
	for _, do := range dos {
		_, err = m.newsSpiderDAO.Insert(&models.NewsSpiderDO{
			OrgId:       do.Id,
			Tag:         do.Tag,
			Category:    do.Category,
			MainTitle:   do.MainTitle,
			SubTitle:    do.SubTitle,
			Summary:     do.Summary,
			Keywords:    do.Keywords,
			SeoKeywords: do.SeoKeywords,
			Url:         do.Url,
			ImageUrl:    do.ImageUrl,
			Content:     do.Content,
			IsHotspot:   do.IsHotspot,
			IsDeleted:   false,
		})
		if err != nil {
			log.Errorf("insert database error [%s]", err.Error())
			return
		}
	}
}

func (m *SchedulerCore) runCronTaskSyncQnaAI(language models.LanguageType, strTable string) {
	lastId, err := m.qaDAO.QueryMaxSyncId(language)
	if err != nil {
		log.Errorf("query max news id from mysql error [%s]", err.Error())
		return
	}
	var dos []*models.WkAigQnaDO
	dos, err = m.wkAigQnaDAO.QueryLatest(strTable, lastId)
	if err != nil {
		log.Errorf("sync from postgresql database error [%s]", err.Error())
		return
	}
	for _, do := range dos {
		_, err = m.qaDAO.Insert(&models.QuestionAnswerDO{
			OrgId:     do.Id,
			Question:  do.Question,
			Answer:    do.Answer,
			IsDeleted: false,
			Language:  language,
			DataTime:  utils.FormatTimestampTZ(do.CreatedTime),
		})
		if err != nil {
			log.Errorf("insert database error [%s]", err.Error())
			return
		}
	}
}

func (m *SchedulerCore) pushNewsToSubscribers(sub *models.NewsSubscribeDO, subscribers []*models.SubscriberDO) {
	var err error
	var toAddrs []string

	cfg, err := m.dictionaryDAO.SelectEmailConfig()
	if err != nil {
		log.Errorf(err.Error())
		return
	}
	news, err := m.newsDAO.QueryById(sub.NewsId)
	if err != nil {
		log.Errorf(err.Error())
		return
	}

	for _, s := range subscribers {
		if len(s.Tags) == 0 {
			toAddrs = append(toAddrs, s.Email)
		} else {
			if m.isContain(news.Tags, s.Tags) {
				toAddrs = append(toAddrs, s.Email)
			}
		}
	}

	strMessage := email.SubscriptionMessage(sub.NewsSubject, news.MainTitle, news.SubTitle, news.Summary, sub.NewsUrl)
	log.Debugf("sending email url [%s] subject [%s] content [%s] to %+v message [%s]", sub.NewsUrl, sub.NewsSubject, sub.NewsUrl, toAddrs, strMessage)
	err = email.SendEmailBcc(cfg, sub.NewsSubject, strMessage, toAddrs...)
	if err != nil {
		log.Errorf("send subscribe email error [%s]", err.Error())
		return
	}
	_, err = m.newsDAO.Update(&models.NewsDO{
		Id:    sub.NewsId,
		State: dao.NewsState_Pushed,
	}, models.NEWS_COLUMN_STATE)
	if err != nil {
		log.Errorf(err.Error())
		return
	}
	_, err = m.subDAO.Update(&models.NewsSubscribeDO{
		Id:       sub.Id,
		IsPushed: true,
	}, models.NEWS_SUBSCRIBE_COLUMN_IS_PUSHED)
	if err != nil {
		log.Errorf(err.Error())
		return
	}
}

func (m *SchedulerCore) isContain(src, dest []string) bool {
	for _, s := range src {
		for _, d := range dest {
			if d == s {
				return true
			}
		}
	}
	return false
}
