package daemon

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"

	"gordenko.dev/dima/flashscore"
	"gordenko.dev/dima/onexbet"
	"gordenko.dev/dima/tipper/model"
	"gordenko.dev/dima/web/api"
	"gordenko.dev/dima/ws"
)

func (s *Daemon) tryLinkToLiveMatch(sportID int, liveMatch onexbet.LiveMatch) {
	matchName := fmt.Sprintf("%s - %s", liveMatch.Home, liveMatch.Away)

	s.logger.Printf("try link: %s\n", matchName)
	// Если уже распознан - пропускаем

	// На всякий пожарный проверка
	//_, ok := s.onexbetMatchToTeamMatch[liveMatch.MatchID]
	//if ok {
	//	s.logger.Printf("try link: %s\n", matchName)
	//	return
	//}

	// Если этот матч уже проверяли - игнорируем
	s.mutex.Lock()

	unrecognizedMatches, ok := s.unrecognizedOnexbetMatches[sportID]
	if !ok {
		s.logger.Printf("unrecognized matches for the sport %d not found\n", sportID)
		return
	}

	if unrecognizedMatches[liveMatch.MatchID] {
		//s.logger.Printf("Match (%s) is unrecognized. Don't repeat identification\n", matchName)
		s.mutex.Unlock()
		return
	}

	s.mutex.Unlock()

	homeTeamCandidates, err := s.fsmodel.ListTeamCandidates(sportID, liveMatch.Home)
	if err != nil {
		s.logger.Printf("fsmodel.ListFootballTeamCandidates: %s\n", err)
		return
	}

	//s.logger.Printf("homeTeamCandidates (for %s): %q", liveMatch.Home, homeTeamCandidates)

	awayTeamCandidates, err := s.fsmodel.ListTeamCandidates(sportID, liveMatch.Away)
	if err != nil {
		s.logger.Printf("fsmodel.ListFootballTeamCandidates: %s\n", err)
		return
	}

	//s.logger.Printf("awayTeamCandidates (for %s): %q", liveMatch.Away, awayTeamCandidates)
	//

	teamMatches, ok := s.teamMatches[sportID]
	if !ok {
		err = fmt.Errorf("Not found matches for the sportID %d", sportID)
		return
	}

	for _, match := range teamMatches {
		var isTeamsSwapped bool

		isIdentified := s.tryIdentifyMatch(match.Home.TeamID, match.Away.TeamID,
			homeTeamCandidates, awayTeamCandidates)

		if !isIdentified {
			// Пробуем опознать если home и away поменять местами
			isIdentified = s.tryIdentifyMatch(match.Home.TeamID, match.Away.TeamID,
				awayTeamCandidates, homeTeamCandidates)

			isTeamsSwapped = true
		}

		if isIdentified {

			if !match.Home.WasLinked {
				err = s.fsmodel.SetTeamWasLinked(match.Home.TeamID)
				if err != nil {
					s.logger.Printf("fsmodel.SetTeamWasLinked: %s; teamID=%s\n",
						err, match.Home.TeamID)
				}
				s.mutex.Lock()
				match.Home.WasLinked = true
				s.mutex.Unlock()
			}

			if !match.Away.WasLinked {
				err = s.fsmodel.SetTeamWasLinked(match.Away.TeamID)
				if err != nil {
					s.logger.Printf("fsmodel.SetTeamWasLinked: %s; teamID=%s\n",
						err, match.Away.TeamID)
				}
				s.mutex.Lock()
				match.Away.WasLinked = true
				s.mutex.Unlock()
			}

			s.mutex.Lock()
			match.IsLinkedToOnexbet = true
			match.OnexbetMatchID = liveMatch.MatchID
			match.OnexbetStatsMatchID = liveMatch.StatsMatchID
			match.Score = liveMatch.Score
			match.ScoreByPeriods = liveMatch.ScoreByPeriods
			match.CurrentPeriod = liveMatch.CurrentPeriod
			match.IsTeamsSwapped = isTeamsSwapped

			s.onexbetMatchToTeamMatch[liveMatch.MatchID] = match
			s.identifiedMatches[liveMatch.MatchID] = true

			s.logger.Printf("Identified: %s; %s; %s\n", matchName, liveMatch.MatchID, match.MatchID)

			// Матч опознан и перешел в inplay
			s.publisher.Publish(
				getChannel(match.SportID),
				WSMessageInplayStatusChanged,
				InplayStatusMessage{
					MatchID:           match.MatchID,
					IsInplay:          match.IsInplay,
					IsLinkedToOnexbet: match.IsLinkedToOnexbet,
				},
			)
			s.mutex.Unlock()
			return
		}
	}

	s.mutex.Lock()
	// Кешируем негативный результат, чтобы не искать в базе ежеминутно
	// Если пользователь распознает новые матчи, - кэш будет очищен
	unrecognizedMatches, ok = s.unrecognizedOnexbetMatches[sportID]
	if !ok {
		return
	}
	unrecognizedMatches[liveMatch.MatchID] = true
	s.mutex.Unlock()

	s.logger.Printf("Match (%s; %s) is not identified. Added to unrecognized\n", matchName, liveMatch.MatchID)
}

func (s *Daemon) tryIdentifyMatch(homeTeamID, awayTeamID string, homeTeamCandidates, awayTeamCandidates []string) (_ bool) {
	for _, candidateHomeTeamID := range homeTeamCandidates {
		if homeTeamID == candidateHomeTeamID {
			// Если нашли совпадение для home - ищем совпадение для away
			for _, candidateAwayTeamID := range awayTeamCandidates {
				if awayTeamID == candidateAwayTeamID {
					// Mатч идентифицирован
					return true
				}
			}
		}
	}
	return
}

func (s *Daemon) onOnexbetWsClientConnected(conn *ws.Conn) {
	s.logger.Printf("Onexbet Ws Client Connected\n")
}

/*
func (s *Daemon) onOnexbetMatchFinished(conn *ws.Conn, in onexbet.WatchReq) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	match, ok := s.onexbetMatchToTeamMatch[in.MatchID]
	if !ok {
		return
	}

	match.IsInplay = false
}
*/

func (s *Daemon) onOnexbetTeamLiveMatches(conn *ws.Conn, in onexbet.LiveMatchesMessage) {
	//s.logger.Println("onOnexbetTeamLiveMatches")
	//s.logger.Printf("onexbetMatchToTeamMatch size: %d\n", len(s.onexbetMatchToTeamMatch))

	sportID, ok := onexbetSportToFlashscoreSport[in.SportID]
	if !ok {
		s.logger.Printf("Bug: unknown OnexbetSport %d\n", in.SportID)
		return
	}

	s.logger.Printf("Onexbet live matches: %d, watching: %d; Inplay: %d\n",
		len(in.Matches), len(in.Watching), len(s.onexbetMatchToTeamMatch))

	liveMap := make(map[string]onexbet.LiveMatch)

	// В цикле рашем 3 задачи:
	// - формируем словарь из live матчей на сайте 1xBet, для удобства дальнейшей обработки
	// - для матчей в inplay проверяем не появился ли liveMatch.StatsMatchID, по которому
	// можно будет узнать результат матча на сайте 1xBet
	// - пытаемся распознать новые матчи
	for _, liveMatch := range in.Matches {
		liveMap[liveMatch.MatchID] = liveMatch

		match, ok := s.onexbetMatchToTeamMatch[liveMatch.MatchID]
		if ok {
			s.logger.Printf("Match (%s - %s) already identified\n", match.Home.CanonicalName, match.Away.CanonicalName)
			// ВАЖНО!
			// liveMatch.StatsMatchID появится когда матч перейдет в лайв.
			if match.OnexbetStatsMatchID == "" && liveMatch.StatsMatchID != "" {
				match.OnexbetStatsMatchID = liveMatch.StatsMatchID
				match.Score = liveMatch.Score
				match.ScoreByPeriods = liveMatch.ScoreByPeriods
				match.CurrentPeriod = liveMatch.CurrentPeriod
			}
		} else {
			if !s.identifiedMatches[liveMatch.MatchID] {
				// Матча нет среди распознанных - пытаемся опознать
				s.tryLinkToLiveMatch(sportID, liveMatch)
			}
		}
	}

	var watchingMap = make(map[string]bool)

	for _, onexbetMatchID := range in.Watching {
		watchingMap[onexbetMatchID] = true

		_, isMatchInInplay := s.onexbetMatchToTeamMatch[onexbetMatchID]
		if !isMatchInInplay {
			// Матча больше нет в inplay. Если 1xbet присылает его в Watching -
			// значит API-запрос Unwatch где-то потерялся. Поэтому отправляем снова.
			s.onexbetAPIClient.Call(api.CallReq{
				FuncName: "unwatch",
				In: onexbet.WatchReq{
					SportID: in.SportID,
					MatchID: onexbetMatchID,
				},
			})
		}
	}

	// Цикл по inplay матчам
	//if sportID == flashscore.Tennis {
	//
	//s.checkInplayTennisMatches(liveMap, watchingMap)
	//} else {
	s.checkInplayTeamMatches(sportID, in.SportID, liveMap, watchingMap)
	//}
}

//func (s *Daemon) checkInplayTennisMatches(liveMap map[string]bool, watchingMap map[string]bool) {
//for onexbetMatchID, match := range s.onexbetMatchToTeamMatch {
//if match.
//}
//}

func (s *Daemon) checkInplayTeamMatches(sportID int, onexbetSportID onexbet.OnexbetSport, liveMap map[string]onexbet.LiveMatch, watchingMap map[string]bool) {
	for onexbetMatchID, match := range s.onexbetMatchToTeamMatch {
		if match.SportID != sportID {
			// Если другой спорт - пропускаем матч
			continue
		}

		liveMatch, isIn1xbetLive := liveMap[onexbetMatchID]

		if !isIn1xbetLive {
			// МАТЧИ могут просто исчезать из ЛАЙВ списка, поэтому просто удалять
			// нельзя! Сперва запросим результат матча у 1xbet и решим что делать.
			//err := s.processMatchDisappearedFromLive(match, onexbetMatchID)
			//if err != nil {
			//	s.logger.Printf("processMatchDisappearedFromLive: %s\n", err)
			//}

			s.logger.Printf("Inplay match (%s - %s) not found in 1xbet live matches. Remove from inplay\n",
				match.Home.CanonicalName, match.Away.CanonicalName)

			s.removeMatchFromInplay(match, onexbetMatchID)
		} else {
			if !watchingMap[onexbetMatchID] {
				// Матч есть в лайве 1xbet (liveMap) - но нет в наблюдаемых. Например,
				// перезагрузился 1xbet демон, или отписались.
				if len(match.Strategies) > 0 {
					// Если еще есть не сработавшие стратегии - начинаем наблюдать.
					//s.logger.Printf("Watch: %d, %s\n", in.SportID, onexbetMatchID)

					// Подписываемся на матчи, которые только что идентифицировали + на те, которые
					// пропали из watch списка (например из-за перезагрузки 1xBET демона)

					for _, strategy := range match.Strategies {
						if strategy.IsNeedToWatch(liveMatch) {
							// Если хотя бы одной стратегии нужно наблюдать за матчем
							// - подписываемся и прерываем цикл
							match.IsInplay = true

							s.onexbetAPIClient.Call(api.CallReq{
								FuncName: "watch",
								In: onexbet.WatchReq{
									SportID: onexbetSportID,
									MatchID: onexbetMatchID,
								},
							})

							s.mutex.Lock()
							// Матч дабавили в inplay
							s.publisher.Publish(
								getChannel(match.SportID),
								WSMessageInplayStatusChanged,
								InplayStatusMessage{
									MatchID:           match.MatchID,
									IsInplay:          match.IsInplay,
									IsLinkedToOnexbet: match.IsLinkedToOnexbet,
								},
							)
							s.mutex.Unlock()
							// ВАЖНО!
							break
						}
					}
				} else {
					s.mutex.Lock()
					//s.logger.Printf("Unwatch: %d, %s\n", in.SportID, onexbetMatchID)
					// Матч больше не интересен
					// Все стратегии сработали - отписываемся от матча
					delete(s.onexbetMatchToTeamMatch, onexbetMatchID)
					match.IsInplay = false

					s.onexbetAPIClient.Call(api.CallReq{
						FuncName: "unwatch",
						In: onexbet.WatchReq{
							SportID: onexbetSportID,
							MatchID: onexbetMatchID,
						},
					})

					// Матч убрали из inplay
					s.publisher.Publish(
						getChannel(match.SportID),
						WSMessageInplayStatusChanged,
						InplayStatusMessage{
							MatchID:           match.MatchID,
							IsInplay:          match.IsInplay,
							IsLinkedToOnexbet: match.IsLinkedToOnexbet,
						},
					)
					s.mutex.Unlock()
				}
			} else {
				// Матч есть в лайве 1xbet (liveMap) и в наблюдаемых - все OK.
				// Ничего не делаем.
			}
		}
	}
}

/*
func (s *Daemon) processMatchDisappearedFromLive(match *TeamMatch, onexbetMatchID string) (err error) {
	if match.OnexbetStatsMatchID == "" {
		// Ситуация возвожна, когда матч появился на короткий срок, OnexbetStatsMatchID еще
		// не получил и затем исчез.
		err = fmt.Errorf("1xBet match %s has empty OnexbetStatsMatchID", onexbetMatchID)
		return
	}

	matchResult, err := onexbet.GetTeamMatchResult(match.OnexbetStatsMatchID)
	if err != nil {
		err = fmt.Errorf("onexbet.GetTeamMatchResult: %s", err)
		return
	}

	if matchResult.Status != onexbet.StatusMatchCompleted {
		// Если матч не завершен - пропускаем. Предполагаем что матч просто
		// временно исчез из лайва
		return
	}
	// Матч завершен - загружаем прогнозы, чтобы выставить результаты
	tips, err := s.model.ListMatchTips(match.MatchID)
	if err != nil {
		err = fmt.Errorf("model.ListMatchTips: %s; matchID=%s", err, match.MatchID)
		return
	}

	if len(tips) > 0 {
		// На один матч может быть несколько прогнозов, поэтому важно знать что результаты
		// всех прогнозов удалось записать в БД. Пока все результаты записать не удастся -
		// это метод будет вызыватся снова и снова.
		for _, tip := range tips {
			err = s.trySetTipResult(tip, matchResult)
			if err != nil {
				err = fmt.Errorf("trySetTipResult: %s; tipID=%d", err, tip.TipID)
				return
			}
		}
	}
	// Результат всех прогнозов по матчу успешно записан либо прогнозов не было.
	// В любом случае - удаляем матч из лайва.
	s.removeMatchFromInplay(match, onexbetMatchID)
	return
}
*/

func (s *Daemon) removeMatchFromInplay(match *TeamMatch, onexbetMatchID string) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	delete(s.onexbetMatchToTeamMatch, onexbetMatchID)
	match.IsInplay = false

	// Матч убрали из inplay
	s.publisher.Publish(
		getChannel(match.SportID),
		WSMessageInplayStatusChanged,
		InplayStatusMessage{
			MatchID:           match.MatchID,
			IsInplay:          match.IsInplay,
			IsLinkedToOnexbet: match.IsLinkedToOnexbet,
		},
	)
}

type WhyNot struct {
	MatchID       string  `json:"matchID"`
	Comment       string  `json:"comment"`
	Time          string  `json:"time"`
	HomeGoals     int     `json:"homeGoals"`
	AwayGoals     int     `json:"awayGoals"`
	GamePace      float64 `json:"gamePace"`
	GamePaceFrame int     `json:"gamePaceFrame"` // count from Zero
}

//func (s *Daemon) onOnexbetTennisLiveMatchData(conn *ws.Conn, upd onexbet.TeamLiveMatchData) {
//	s.mutex.Lock()
//	defer s.mutex.Unlock()

//	s.logger.Printf("tennis MatchData: %s\n", upd.MatchID)

//}
/*
type TeamLiveMatchData struct {
	MatchID              string      `json:"matchID"`
	HomeAttacks          int         `json:"homeAttacks"`
	HomeDangerousAttacks int         `json:"homeDangerousAttacks"`
	HomeShotsOnTarget    int         `json:"homeShotsOnTarget"`
	HomeShotsOffTarget   int         `json:"homeShotsOffTarget"`
	AwayAttacks          int         `json:"awayAttacks"`
	AwayDangerousAttacks int         `json:"awayDangerousAttacks"`
	AwayShotsOnTarget    int         `json:"awayShotsOnTarget"`
	AwayShotsOffTarget   int         `json:"awayShotsOffTarget"`
	CurrentTime          int         `json:"currentTime"`
	HomeGoals            int         `json:"homeGoals"`
	AwayGoals            int         `json:"awayGoals"`
	Winner               *Winner3Way `json:"winner"`
	Total                Total       `json:"total"`
	Handicap             Handicap    `json:"handicap"`
	IndividualTotalHome  Total       `json:"individualTotalHome"`
	IndividualTotalAway  Total       `json:"individualTotalAway"`
	H1                   Half        `json:"h1"` // 1st half
	// tennis
	Winner2Way *Winner2Way `json:"winner2way"`

	// score
	Score          Score          `json:"score"`
	ScoreByPeriods map[int]Score  `json:"scoreByPeriods"`
	CurrentPeriod  int            `json:"currentPeriod"`
	Periods        map[int]Period `json:"periods"`
}
*/

func swapTeamLiveMatchData(d onexbet.TeamLiveMatchData) onexbet.TeamLiveMatchData {
	d.AwayAttacks, d.HomeAttacks = d.HomeAttacks, d.AwayAttacks
	d.AwayDangerousAttacks, d.HomeDangerousAttacks = d.HomeDangerousAttacks, d.AwayDangerousAttacks
	d.AwayShotsOnTarget, d.HomeShotsOnTarget = d.HomeShotsOnTarget, d.AwayShotsOnTarget
	d.AwayShotsOffTarget, d.HomeShotsOffTarget = d.HomeShotsOffTarget, d.AwayShotsOffTarget
	d.AwayGoals, d.HomeGoals = d.HomeGoals, d.AwayGoals

	if d.Winner != nil {
		d.Winner.Away, d.Winner.Home = d.Winner.Home, d.Winner.Away
	}

	if d.Winner2Way != nil {
		d.Winner2Way.Away, d.Winner2Way.Home = d.Winner2Way.Home, d.Winner2Way.Away
	}

	d.Handicap.Away, d.Handicap.Home = d.Handicap.Home, d.Handicap.Away
	d.IndividualTotalAway, d.IndividualTotalHome = d.IndividualTotalHome, d.IndividualTotalAway

	d.H1.IndividualTotalAway, d.H1.IndividualTotalHome = d.H1.IndividualTotalHome, d.H1.IndividualTotalAway

	d.Score.Away, d.Score.Home = d.Score.Home, d.Score.Away

	for periodNumber, score := range d.ScoreByPeriods {
		score.Away, score.Home = score.Home, score.Away

		d.ScoreByPeriods[periodNumber] = score
	}

	for periodNumber, period := range d.Periods {
		period.Handicap.Away, period.Handicap.Home = period.Handicap.Home, period.Handicap.Away

		d.Periods[periodNumber] = period
	}

	return d
}

func (s *Daemon) onOnexbetTeamLiveMatchData(conn *ws.Conn, upd onexbet.TeamLiveMatchData) {
	s.mutex.Lock()
	defer s.mutex.Unlock()

	s.logger.Printf("MatchData: %s\n", upd.MatchID)

	// маппинг -
	match, ok := s.onexbetMatchToTeamMatch[upd.MatchID]
	if !ok {
		return
	}

	if match.IsTeamsSwapped {
		upd = swapTeamLiveMatchData(upd)
	}

	//if match.SportID == flashscore.Handball {
	//x, _ := json.MarshalIndent(upd, "", "    ")
	//fmt.Printf("MatchID: %s (1xbet MatchID: %s), Time: %d, HomeGoals: %d, AwayGoals: %d\n",
	//	match.MatchID, upd.MatchID, upd.CurrentTime, upd.HomeGoals, upd.AwayGoals)
	//}

	channel := getChannel(match.SportID)

	//s.logger.Printf("Onexbet TeamLiveMatchData: sportID %d, channel %s\n", match.SportID, channel)

	var (
		triggeredStrategyIDs []int64
		comments             []string
		strategyIDs          []int
	)

	for strategyID := range match.Strategies {
		strategyIDs = append(strategyIDs, int(strategyID))
	}

	sort.Ints(strategyIDs)

	for _, strategyID := range strategyIDs {

		strategy := match.Strategies[int64(strategyID)]
		// Если по стратегии уже дали прогноз
		//if match.EndedStrategies[strategy.ID()] {
		//	continue
		//}

		whyNot, bet, code := strategy.GetTip(match, upd)

		switch code {
		case Waiting:
			// pass
			//s.logger.Printf("Waiting: %s\n", whyNot)

			// Отправить в websocket
			comments = append(comments, fmt.Sprintf("%s:<br>%s", strategy.ShortName(), whyNot))

		case Unwatch:
			s.logger.Printf("Unwatch: %s\n", whyNot)

			triggeredStrategyIDs = append(triggeredStrategyIDs, strategy.ID())

			comments = append(comments, fmt.Sprintf("%s:<br>%s", strategy.ShortName(), whyNot))

		case Bet:

			triggeredStrategyIDs = append(triggeredStrategyIDs, strategy.ID())

			var err error
			//var tip model.Tip
			//var tipPrice float64

			tip := model.Tip{
				SportID:             int64(match.SportID),
				StrategyID:          strategy.ID(),
				MatchID:             match.MatchID,
				TipTime:             time.Now().Unix(),
				ChampID:             match.Champ.ChampID,
				ChampName:           match.Champ.Name,
				MatchName:           fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
				Market:              bet.Market,
				Side:                bet.Side,
				Param:               bet.Param,
				Price:               bet.Price,
				CurrentMatchTime:    upd.CurrentTime,
				OnexbetStatsMatchID: match.OnexbetStatsMatchID,
				IsTeamsSwapped:      match.IsTeamsSwapped,
			}

			champRatings, ok := s.champRatings[match.SportID]
			if ok {
				// обновляем рейтинги чемпионатов
				key := ChampRatingKey{
					ChampID:    tip.ChampID,
					StrategyID: tip.StrategyID,
				}

				rating, ok := champRatings[key]
				if ok {
					//if (rating.Won+rating.Lost) >= 6 && rating.GetAccuracy() >= 75 {
					if rating.GetAccuracy() >= 70 {
						tip.IsRated = true
					}
				}
			}

			s.logger.Printf("Bet: %#v\n", tip)

			tip.TipID, err = s.model.AddTip(tip)
			if err != nil {
				s.logger.Printf("model.AddTip: %s\n", err)
			} else {
				// Отправить в websocket
				// Добавили новый Tip
				s.publisher.Publish(channel, WSMessageTipsChanged, nil)

				if match.SportID == flashscore.Football {
					// Для футбола только рейтинговые чемпионаты
					// Отправить в телеграм
					if strategy.IsNotifyInTelegram() && tip.IsRated {
						// Отправлять ли в телеграм
						msg := fmt.Sprintf(strategy.GetTelegramTipPattern(), tip.TipID, strategy.ID(),
							match.Zone.Name, match.Champ.Name,
							match.Home.CanonicalName, match.Away.CanonicalName,
							bet.Price)

						telegramChatID, hasChat := sportToTelegramChatID[match.SportID]
						if hasChat {
							err = sendMessageToTelegramGroup(telegramChatID, msg)
							if err != nil {
								s.logger.Printf("send tip to Telegram: %s\n", err)
							}
						} else {
							s.logger.Printf("Sport %d hasn't telegram chat\n", match.SportID)
						}
					}
				} else if match.SportID == flashscore.Handball {
					if strategy.IsNotifyInTelegram() && tip.IsRated {
						param, err := strconv.ParseFloat(bet.Param, 64)
						if err != nil {
							s.logger.Printf("Wrong bet param: %s; param=%s\n", err, bet.Param)
						} else {

							var betterParam1, betterParam2 float64

							switch strategy.(type) {
							case HandballFTOver, HandballP1Over:
								betterParam1 = param - 1
								betterParam2 = param - 2

							case HandballFTUnder, HandballP1Under:
								betterParam1 = param + 1
								betterParam2 = param + 2

							default:
								panic(fmt.Sprintf("Unknown strategy %#v\n", strategy))
							}
							// Для гандбола отправляем все прогнозы

							msg := fmt.Sprintf(strategy.GetTelegramTipPattern(), tip.TipID, strategy.ID(),
								match.Zone.Name, match.Champ.Name,
								match.Home.CanonicalName, match.Away.CanonicalName,
								param, bet.Price, param, betterParam1, betterParam2)

							telegramChatID, hasChat := sportToTelegramChatID[match.SportID]
							if hasChat {
								err = sendMessageToTelegramGroup(telegramChatID, msg)
								if err != nil {
									s.logger.Printf("send tip to Telegram: %s\n", err)
								}
							} else {
								s.logger.Printf("Sport %d hasn't telegram chat\n", match.SportID)
							}
						}
					}
				} else if match.SportID == flashscore.Tennis {
					if strategy.IsNotifyInTelegram() {
						msg := fmt.Sprintf(bet.TelegramMessage, tip.TipID)

						telegramChatID, hasChat := sportToTelegramChatID[match.SportID]
						if hasChat {
							err = sendMessageToTelegramGroup(telegramChatID, msg)
							if err != nil {
								s.logger.Printf("send tip to Telegram: %s\n", err)
							}
						} else {
							s.logger.Printf("Sport %d hasn't telegram chat\n", match.SportID)
						}
					}
				}
			}
		}
	}

	for _, strategyID := range triggeredStrategyIDs {
		delete(match.Strategies, strategyID)
	}

	if len(match.Strategies) == 0 {
		// Все стратегии сработали - отписываемся от матча

		s.logger.Printf("Unwatch: %d, %s\n", match.SportID, upd.MatchID)

		// Матч больше не интересен
		delete(s.onexbetMatchToTeamMatch, upd.MatchID)
		match.IsInplay = false

		s.onexbetAPIClient.Call(api.CallReq{
			FuncName: "unwatch",
			In: onexbet.WatchReq{
				SportID: flashscoreSportToOnexbetSport[match.SportID],
				MatchID: upd.MatchID, // onexbetMatchID
			},
		})
	}

	if match.SportID == flashscore.Tennis {
		if len(comments) > 0 {
			s.publisher.Publish(
				channel,
				WSMessageWhyNot,
				WhyNot{
					MatchID:       match.MatchID,
					Comment:       strings.Join(comments, "<br><br>"),
					Time:          "",
					HomeGoals:     upd.HomeGoals,
					AwayGoals:     upd.AwayGoals,
					GamePace:      match.GamePace,
					GamePaceFrame: match.GamePaceFrame,
				})
		}
	} else {
		if len(comments) > 0 {
			s.publisher.Publish(
				channel,
				WSMessageWhyNot,
				WhyNot{
					MatchID:       match.MatchID,
					Comment:       strings.Join(comments, "<br><br>"),
					Time:          fmt.Sprintf("%s", time.Duration(upd.CurrentTime)*time.Second),
					HomeGoals:     upd.HomeGoals,
					AwayGoals:     upd.AwayGoals,
					GamePace:      match.GamePace,
					GamePaceFrame: match.GamePaceFrame,
				})
		}
	}
}

/*
	var tipTextPattern string

	switch strategyID {
	case 1:
		tipTextPattern = tipTextAlgo1Pattern

	case 2:
		tipTextPattern = tipTextAlgo2Pattern

	case 3:
		tipTextPattern = tipTextAlgo3Pattern

	case 100:
		tipTextPattern = tipTextAlgo100Pattern
	}
*/

/*
switch strategyID {
			case 1:
				var price float64
				for _, over := range upd.H1.Total.Over {
					if over.Param == "0.5" {
						price = over.Price
					}
				}

				tipPrice = price

				tip = model.Tip{
					SportID:             SportFootball,
					StrategyID:          int64(strategyID),
					MatchID:             match.MatchID,
					TipTime:             time.Now().Unix(),
					ChampName:           match.Champ.Name,
					MatchName:           fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
					Market:              MarketTotal1stHalf,
					Side:                SideOver,
					Param:               "0.5",
					Price:               price,
					CurrentMatchTime:    upd.CurrentTime,
					OnexbetStatsMatchID: match.OnexbetStatsMatchID,
				}

			case 2:
				var price float64
				for _, over := range upd.Total.Over {
					if over.Param == "0.5" {
						price = over.Price
					}
				}

				tipPrice = price

				tip = model.Tip{
					SportID:             SportFootball,
					StrategyID:          int64(strategyID),
					MatchID:             match.MatchID,
					TipTime:             time.Now().Unix(),
					ChampName:           match.Champ.Name,
					MatchName:           fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
					Market:              MarketTotal,
					Side:                SideOver,
					Param:               "0.5",
					Price:               price,
					CurrentMatchTime:    upd.CurrentTime,
					OnexbetStatsMatchID: match.OnexbetStatsMatchID,
				}

			case 3:
				var price float64
				for _, over := range upd.Total.Over {
					if over.Param == "1.5" {
						price = over.Price
					}
				}

				tipPrice = price

				tip = model.Tip{
					SportID:             SportFootball,
					StrategyID:          int64(strategyID),
					MatchID:             match.MatchID,
					TipTime:             time.Now().Unix(),
					ChampName:           match.Champ.Name,
					MatchName:           fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
					Market:              MarketTotal,
					Side:                SideOver,
					Param:               "1.5",
					Price:               price,
					CurrentMatchTime:    upd.CurrentTime,
					OnexbetStatsMatchID: match.OnexbetStatsMatchID,
				}

			case 100:
				var price float64
				for _, over := range upd.H1.Total.Over {
					if over.Param == "0.5" {
						price = over.Price
					}
				}

				tipPrice = price

				tip = model.Tip{
					SportID:             SportFootball,
					StrategyID:          int64(strategyID),
					MatchID:             match.MatchID,
					TipTime:             time.Now().Unix(),
					ChampName:           match.Champ.Name,
					MatchName:           fmt.Sprintf("%s - %s", match.Home.CanonicalName, match.Away.CanonicalName),
					Market:              MarketTotal1stHalf,
					Side:                SideOver,
					Param:               "0.5",
					Price:               price,
					CurrentMatchTime:    upd.CurrentTime,
					OnexbetStatsMatchID: match.OnexbetStatsMatchID,
				}
			}
*/
