package daemon

import (
	"fmt"
	"math"
	"sort"
	"strconv"
	"strings"

	"gordenko.dev/dima/flashscore"
	"gordenko.dev/dima/onexbet"
	"gordenko.dev/dima/tipper/model"
)

/*
type TennisMatch struct {
	flashscoreMatchID string
	totalBreakpoints  int
	lastPrice         float64
	last3rdSetTotal   int
	closeCh           chan struct{}
}

func NewTennisMatch() *TennisMatch {
	s := new(TennisMatch)
	s.closeCh = make(chan struct{})
	return s
}

func (s *TennisMatch) checker() {
	ticker := time.NewTicker(7 * time.Second)

	for {
		select {
		case <-ticker.C:
			s.loadMatchStat()

		case <-s.closeCh:
			ticker.Stop()
			// drain channel?
			return
		}
	}
}

func (s *TennisMatch) loadMatchStat() {
	stat, err := flashscore.LoadTennisMatchStat(s.flashscoreMatchID)
	if err != nil {
		// log
		return
	}

	if stat.HomeBreakpoints != nil && stat.AwayBreakpoints != nil {
		s.totalBreakpoints = stat.HomeBreakpoints.Qty + stat.AwayBreakpoints.Qty
	}

	s.tryGetTip()
}

func (s *TennisMatch) tryGetTip() {

}

func (s *TennisMatch) Close() {
	s.closeCh <- struct{}{}
}
*/

// test strategy
// ТМ в 3 сете
// 1) Женщины все покрытия
// 2) Мужчины (кроме быстрых покрытий) (Грунт!!!)

// Условия:
// 1) 1 и 2 сет ТБ 10.5 геймов (7:5; 6:7) сумарно не мение 6 и более (брейк поинтов) майскор
// 2) 3 сет при счете 1:1 смотрим 10.5 меньше но не позднее 2:2
type Tennis3rdSetTotalUnder struct {
	id               int64
	notifyInTelegram bool
	isChampAccepted  func(string) bool
}

func NewTennis3rdSetTotalUnder(opt StrategyOptions) Tennis3rdSetTotalUnder {
	if opt.ID == 0 {
		panic("StrategyID not defined")
	}
	s := Tennis3rdSetTotalUnder{}
	s.id = opt.ID
	s.notifyInTelegram = opt.NotifyInTelegram
	s.isChampAccepted = opt.IsChampAccepted
	return s
}

func (s Tennis3rdSetTotalUnder) ID() int64 {
	return s.id
}

func (s Tennis3rdSetTotalUnder) ShortName() string {
	return fmt.Sprintf("#%d S3 Under 10.5", s.id)
}

func (s Tennis3rdSetTotalUnder) IsNotifyInTelegram() bool {
	return s.notifyInTelegram
}

func (s Tennis3rdSetTotalUnder) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool {
	if liveMatch.Score.Home == 1 && liveMatch.Score.Away == 1 {
		return true
	}
	return false
}

func (s Tennis3rdSetTotalUnder) IsInteresting(_ *TeamMatch) (interface{}, bool) {
	return nil, true
}
func (s Tennis3rdSetTotalUnder) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (string, *BetDetails, TipCode) {
	if upd.CurrentPeriod < 3 {
		return "не 3й сет", nil, Waiting
	}

	if upd.CurrentPeriod > 3 {
		return "3й сет завершен", nil, Unwatch
	}

	s1score, ok := upd.ScoreByPeriods[1]
	if !ok {
		// нет счета 1 сета
		return "счет 1го сета не найден", nil, Waiting
	}

	s1total := s1score.Home + s1score.Away

	if float64(s1total) < 11.5 {
		return fmt.Sprintf("тотал 1го сета %d", s1total), nil, Unwatch
	}

	s2score, ok := upd.ScoreByPeriods[2]
	if !ok {
		// нет счета 2 сета
		return "счет 2го сета не найден", nil, Waiting
	}

	s2total := s2score.Home + s2score.Away

	if float64(s2total) < 11.5 {
		return fmt.Sprintf("тотал 2го сета %d", s2total), nil, Unwatch
	}

	s3score, ok := upd.ScoreByPeriods[3]
	if !ok {
		// нет счета 3 сета
		return "счет 3го сета не найден", nil, Waiting
	}

	if s3score.Home > 2 || s3score.Away > 2 {
		return "счет в 3м сете превысил 2:2", nil, Unwatch
	}

	if s3score.Home == 0 || s3score.Away == 0 {
		return "ждем счет в 3м сете: 1:1, 1:2, 2:1 или 2:2", nil, Waiting
	}

	sex := flashscore.ParseTennisSexFromChampName(match.FullChampName)
	if sex == "" {
		return "не удалось определить мужчины или женщины играют", nil, Unwatch
	}

	if sex == flashscore.SexMen {
		surface := flashscore.ParseTennisSurfaceFromChampName(match.FullChampName)
		if surface != flashscore.SurfaceClay {
			return fmt.Sprintf("слишком быстрое покрытие для мужиков: %s", surface), nil, Unwatch
		}
	}

	if match.TwoSetsBreakpoints == 0 {

		matchStat, err := flashscore.LoadTennisMatchStat(match.MatchID)
		if err != nil {
			return fmt.Sprintf("не удалось загрузить статистику по брейкпоинтам: %s", err),
				nil, Waiting
		}

		var breakpoints int

		// Запросить стату по брейкпоинтам с сайта Flashscore
		if matchStat.HomeBreakpoints != nil {
			breakpoints += matchStat.HomeBreakpoints.Qty
		}

		if matchStat.AwayBreakpoints != nil {
			breakpoints += matchStat.AwayBreakpoints.Qty
		}

		match.TwoSetsBreakpoints = breakpoints
	}

	if match.TwoSetsBreakpoints < 6 {
		return fmt.Sprintf("к-во брейкпоинтов < 6: %d", match.TwoSetsBreakpoints), nil, Unwatch
	}

	// Поиски тотала с коэф. >= 1.5
	set3, ok := upd.Periods[3]
	if !ok {
		return "1xbet не дает тотал 3го сета", nil, Waiting
	}

	under := set3.Total.Under

	sort.Slice(under, func(i, j int) bool {
		if under[i].ParamFloat > under[j].ParamFloat {
			return true
		}
		return false
	})

	for _, offer := range under {
		if offer.ParamFloat > 10.4 {
			if offer.Price >= 1.5 {

				telegramMessage := fmt.Sprintf(`Сигнал # %%d.
Алгоритм %d. Тотал меньше в 3м сете.
%s
%s - %s

Точка входа %.1f кф. %.3f

Рекомендация: тотал меньше %.1f и выше (+%.1f; +%.1f)`,
					s.id,
					match.Champ.Name,
					match.Home.CanonicalName, match.Away.CanonicalName,
					offer.ParamFloat, offer.Price,
					offer.ParamFloat, offer.ParamFloat+1, offer.ParamFloat+2)

				return "", &BetDetails{
					Market:          MarketTotal,
					Side:            SideUnder,
					Param:           offer.Param,
					Price:           offer.Price,
					TelegramMessage: telegramMessage,
				}, Bet
			}
		}
	}

	return "не найден тотал 10.5 и выше с коэф. 1.5 и выше", nil, Waiting

}
func (s Tennis3rdSetTotalUnder) GetResult(tip model.Tip, result onexbet.TeamMatchResult) (_ *TipResult) {
	score, ok := result.ScoreByPeriods[3]
	if !ok {
		// Сперва проверяем что счет есть.
		return
	}

	// Проверяем что сет завершен
	if !isTennisSetCompleted(score) {
		return
	}

	total := float64(score.HomePoints + score.AwayPoints)

	param, err := strconv.ParseFloat(tip.Param, 64)
	if err != nil {
		fmt.Printf("Parse tip param: %s; param=%s", err, tip.Param)
		return
	}

	resultText := fmt.Sprintf(`3й сет: %d-%d`, score.HomePoints, score.AwayPoints)

	if isFloatEqual(param, total, 0.1) {
		// Совпасть могут только целые форы +3, +4
		return &TipResult{
			Status: model.Void,
			Result: resultText,
		}
	}

	if total < param {
		return &TipResult{
			Status: model.Won,
			Result: resultText,
		}
	} else {
		return &TipResult{
			Status: model.Lost,
			Result: resultText,
		}
	}

}

func (s Tennis3rdSetTotalUnder) GetTelegramTipPattern() string {
	return ""
}

//////////////////////////////////

// Плюсовая фора на Аутсайдера.

// 1) Определяем Фаворита до лайва (коэф. <= 1.5)

// В лайве наблюдаем и ждем выполнения 2 условий:
// 2) Фаворит выигрывает 1й сет с тоталом < 8.5
// 3) В первом сете на фаворита давали фору -2.5 по ровной линии (коэф. от 1.75 до 1.95)

// Прогноз:
// Если на фаворита в 1м сете давали фору -2.5 в ровной линии,
// то входим +3.5 на аутсайдера во 2м сете. Если давали -3.5 то входим +4.5;
// -4.5 то входим +5.5

// Коэф. входа от 1.5 до 2.5, текущий счет во 2м сете не имеет значения.

type Tennis2ndSetPlusHandicap struct {
	id               int64
	notifyInTelegram bool
	isChampAccepted  func(string) bool
}

func NewTennis2ndSetPlusHandicap(opt StrategyOptions) Tennis2ndSetPlusHandicap {
	if opt.ID == 0 {
		panic("StrategyID not defined")
	}
	s := Tennis2ndSetPlusHandicap{}
	s.id = opt.ID
	s.notifyInTelegram = opt.NotifyInTelegram
	s.isChampAccepted = opt.IsChampAccepted
	return s
}

func (s Tennis2ndSetPlusHandicap) ID() int64 {
	return s.id
}

func (s Tennis2ndSetPlusHandicap) ShortName() string {
	return fmt.Sprintf("#%d S2 Outsider Handicap", s.id)
}

func (s Tennis2ndSetPlusHandicap) IsNotifyInTelegram() bool {
	return s.notifyInTelegram
}

func (s Tennis2ndSetPlusHandicap) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool {
	if liveMatch.CurrentPeriod == 1 || liveMatch.CurrentPeriod == 2 {
		return true
	}
	return false
}

func (s Tennis2ndSetPlusHandicap) IsInteresting(match *TeamMatch) (interface{}, bool) {
	// Поиск фаворита
	for _, offer := range match.Odds.Winner2Way {
		if offer.Home <= 1.5 {
			match.FavoriteSide = FavoriteHome

			return fmt.Sprintf("фаворит %s, коэф. %.2f",
				match.Home.CanonicalName, offer.Home), true
		} else if offer.Away <= 1.5 {
			match.FavoriteSide = FavoriteAway

			return fmt.Sprintf("фаворит %s, коэф. %.2f",
				match.Away.CanonicalName, offer.Away), true
		}
	}
	return nil, false
}
func (s Tennis2ndSetPlusHandicap) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (string, *BetDetails, TipCode) {
	if upd.CurrentPeriod == 1 {
		// поиски форы на 1й сет по ровной линии
		p, ok := upd.Periods[1]
		if !ok {
			return "ставки на 1й сет не найдены", nil, Waiting
		}

		var offers []onexbet.ParamOffer

		switch match.FavoriteSide {
		case FavoriteHome:
			offers = p.Handicap.Home

		case FavoriteAway:
			offers = p.Handicap.Away

		default:
			return "условие 'фаворит в матче' не выполнено", nil, Unwatch
		}

		// Масимальные минусовые форы первыми
		SortOffersByParamMinMax(offers)

		for _, offer := range offers {
			switch offer.Param {
			case "-2.5", "-3.0", "-3.5", "-4.0", "-4.5", "-5.0":
				if offer.Price >= 1.75 && offer.Price <= 1.95 {
					local := match.MaxFavoritHandicapIn1stSet
					if local == nil {
						match.MaxFavoritHandicapIn1stSet = &onexbet.ParamOffer{
							Price:      offer.Price,
							Param:      offer.Param,
							ParamFloat: offer.ParamFloat,
						}
					} else {
						// нужно сравнить параметры
						if offer.ParamFloat < local.ParamFloat {
							// Фора стала еще больше - обновляем, ибо размер форы влияет
							// на плюсовую фору в прогнозе
							local.Param = offer.Param
							local.ParamFloat = offer.ParamFloat
							local.Price = offer.Price
						}
					}
				}

			case "-5.5":
				if offer.Price >= 1.75 && offer.Price <= 1.95 {
					return "1й сет, фора на фаворита -5.5", nil, Unwatch
				}
			}
		}
	}

	if upd.CurrentPeriod < 2 {
		return "не 2й сет", nil, Waiting
	}

	if upd.CurrentPeriod > 2 {
		return "2й сет завершен", nil, Unwatch
	}

	s1score, ok := upd.ScoreByPeriods[1]
	if !ok {
		// нет счета 1 сета
		return "счет 1го сета не найден", nil, Waiting
	}

	s1total := s1score.Home + s1score.Away

	if float64(s1total) > 8.5 {
		return fmt.Sprintf("тотал 1го сета %d", s1total), nil, Unwatch
	}

	// Поиски тотала с коэф. >= 1.5
	if match.MaxFavoritHandicapIn1stSet == nil {
		return "фора на фаворита в 1м сете от -2.5 и выше не была найдена", nil, Unwatch
	}

	p, ok := upd.Periods[2]
	if !ok {
		return "ставки на 2й сет не найдены", nil, Waiting
	}

	// Находим список фор на аутсайдера
	var (
		offers  []onexbet.ParamOffer
		betSide int
	)

	switch match.FavoriteSide {
	case FavoriteHome:
		offers = p.Handicap.Away
		betSide = SideAway

		if (s1score.Home - s1score.Away) <= 0 {
			return "фаворит 1й сет проиграл", nil, Unwatch
		}

	case FavoriteAway:
		offers = p.Handicap.Home
		betSide = SideHome

		if (s1score.Away - s1score.Home) <= 0 {
			return "фаворит 1й сет проиграл", nil, Unwatch
		}

	default:
		return "условие 'фаворит в матче' не выполнено", nil, Unwatch
	}

	// Масимальные плюсовые форы первыми
	SortOffersByParamMaxMin(offers)

	// -2.5 превращаем в 3.4, -3 превращаем в 3.9
	minPlusHandicap := math.Abs(match.MaxFavoritHandicapIn1stSet.ParamFloat) - 0.1 + 1

	for _, offer := range offers {
		if offer.Price < 1.5 || offer.Price > 2.5 {
			continue
		}

		if offer.ParamFloat > minPlusHandicap {

			var sideName string

			switch betSide {
			case SideHome:
				sideName = match.Home.CanonicalName

			case SideAway:
				sideName = match.Away.CanonicalName
			}

			var additionalHandicaps string

			if offer.ParamFloat < 3.6 {
				// +3.5, +2.5
				additionalHandicaps = fmt.Sprintf("и выше (+%.1f; +%.1f)",
					offer.ParamFloat+1, offer.ParamFloat+2)
			} else if offer.ParamFloat < 4.6 {
				// // +4.5, +4.0
				additionalHandicaps = fmt.Sprintf("и выше (+%.1f)",
					offer.ParamFloat+1)
			}

			telegramMessage := fmt.Sprintf(`Сигнал # %%d.
Алгоритм %d. Плюсовая фора на аутсайдера во 2м сете.
%s
%s - %s

Точка входа +%.1f кф. %.3f

Рекомендация: %s +%.1f %s`,
				s.id,
				match.Champ.Name,
				match.Home.CanonicalName, match.Away.CanonicalName,
				offer.ParamFloat, offer.Price,
				sideName, offer.ParamFloat, additionalHandicaps)

			return "", &BetDetails{
				Market:          MarketHandicap,
				Side:            betSide,
				Param:           offer.Param,
				Price:           offer.Price,
				TelegramMessage: telegramMessage,
			}, Bet
		}
	}

	msg := fmt.Sprintf("не найдена фора %.1f и выше с коэф. от 1.5 до 2.5",
		math.Abs(match.MaxFavoritHandicapIn1stSet.ParamFloat)+1)

	return msg, nil, Waiting

}
func (s Tennis2ndSetPlusHandicap) GetResult(tip model.Tip, result onexbet.TeamMatchResult) (_ *TipResult) {
	score, ok := result.ScoreByPeriods[2]
	if !ok {
		// Сперва проверяем что счет есть.
		return
	}

	// Проверяем что сет завершен
	if !isTennisSetCompleted(score) {
		return
	}

	var handicap float64

	switch tip.Side {
	case SideHome:
		handicap = float64(score.AwayPoints - score.HomePoints)

	case SideAway:
		handicap = float64(score.HomePoints - score.AwayPoints)
	}

	param, err := strconv.ParseFloat(tip.Param, 64)
	if err != nil {
		fmt.Printf("Parse tip param: %s; param=%s", err, tip.Param)
		return
	}

	resultText := fmt.Sprintf(`2й сет: %d-%d`, score.HomePoints, score.AwayPoints)

	if isFloatEqual(param, handicap, 0.1) {
		// Совпасть могут только целые форы +3, +4
		return &TipResult{
			Status: model.Void,
			Result: resultText,
		}
	}

	// Если форы не равны, то одна из них больше на 0.5 минимум, поэтому можно
	// просто сравнить

	if handicap < 0.1 {
		// если меньше нуля (наш теннисит выиграл 2й сет)
		return &TipResult{
			Status: model.Won,
			Result: resultText,
		}
	}

	if handicap < param {
		return &TipResult{
			Status: model.Won,
			Result: resultText,
		}
	} else {
		return &TipResult{
			Status: model.Lost,
			Result: resultText,
		}
	}
}

func (s Tennis2ndSetPlusHandicap) GetTelegramTipPattern() string {
	return ""
}

////////////////////////////////////////

// Алгоритм 3 теннис

// Плюсовая фора 2 сет
// Равные соперники

// Это ровная линия в кофициентах
// 1.9 - 1.8
// 1.87 - 1.87
// 1.95 - 1.85

// Все покрытия: но в идеале Хард и грунт

// Только мужчины

// Условия:
// Один спортсмен должен выиграть сет с большим преимуществом
// Идеал:
// 6:0
// 6:1
// Можно и 6:2

// Победитель первого сета должен получить КФ на победу 1.5

// Во втором сете мы ставим на плюсовую фору второго спортсмена
// +2.5
// +3.5
// С КФ не ниже 1.5
// Диапазон 1.5 - 1.7

type Tennis2ndSetPlusHandicapAny struct {
	id               int64
	notifyInTelegram bool
	isChampAccepted  func(string) bool
}

func NewTennis2ndSetPlusHandicapAny(opt StrategyOptions) Tennis2ndSetPlusHandicapAny {
	if opt.ID == 0 {
		panic("StrategyID not defined")
	}
	s := Tennis2ndSetPlusHandicapAny{}
	s.id = opt.ID
	s.notifyInTelegram = opt.NotifyInTelegram
	s.isChampAccepted = opt.IsChampAccepted
	return s
}

func (s Tennis2ndSetPlusHandicapAny) ID() int64 {
	return s.id
}

func (s Tennis2ndSetPlusHandicapAny) ShortName() string {
	return fmt.Sprintf("#%d S2 Equal Handicap", s.id)
}

func (s Tennis2ndSetPlusHandicapAny) IsNotifyInTelegram() bool {
	return s.notifyInTelegram
}

func (s Tennis2ndSetPlusHandicapAny) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool {
	if liveMatch.CurrentPeriod == 1 || liveMatch.CurrentPeriod == 2 {
		return true
	}
	return false
}

func (s Tennis2ndSetPlusHandicapAny) IsInteresting(match *TeamMatch) (interface{}, bool) {
	// Поиск фаворита
	for _, offer := range match.Odds.Winner2Way {
		if offer.Home >= 1.75 && offer.Home <= 1.97 {
			return nil, true
		} else if offer.Away >= 1.75 && offer.Away <= 1.97 {
			return nil, true
		}
	}
	return nil, false
}
func (s Tennis2ndSetPlusHandicapAny) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (string, *BetDetails, TipCode) {
	if upd.CurrentPeriod < 2 {
		return "не 2й сет", nil, Waiting
	}

	if upd.CurrentPeriod > 2 {
		return "2й сет завершен", nil, Unwatch
	}

	s1score, ok := upd.ScoreByPeriods[1]
	if !ok {
		// нет счета 1 сета
		return "счет 1го сета не найден", nil, Waiting
	}

	s1total := s1score.Home + s1score.Away

	if float64(s1total) > 8.5 {
		return fmt.Sprintf("тотал 1го сета %d", s1total), nil, Unwatch
	}

	var (
		favorite            FavoriteSide
		favoriteWinnerPrice float64
	)

	if upd.Winner2Way == nil {
		return "коэф. на победу в матче не найден", nil, Waiting
	}

	if s1score.Home > s1score.Away {
		favorite = FavoriteHome
		favoriteWinnerPrice = upd.Winner2Way.Home
	} else {
		favorite = FavoriteAway
		favoriteWinnerPrice = upd.Winner2Way.Away
	}

	if favoriteWinnerPrice > 1.5 {
		return fmt.Sprintf("коэф. на победу в матче %.3f", favoriteWinnerPrice), nil, Waiting
	}

	sex := flashscore.ParseTennisSexFromChampName(match.FullChampName)
	if sex == "" {
		return "не удалось определить мужчины или женщины играют", nil, Unwatch
	}

	if sex == flashscore.SexWomen {
		champName := strings.ToLower(match.FullChampName)
		if !strings.Contains(champName, "wta") {
			return "женский матч должен играться на турнире серии WTA", nil, Unwatch
		}
	}

	p, ok := upd.Periods[2]
	if !ok {
		return "ставки на 2й сет не найдены", nil, Waiting
	}

	// Находим список фор на аутсайдера
	var (
		offers  []onexbet.ParamOffer
		betSide int
	)

	switch favorite {
	case FavoriteHome:
		offers = p.Handicap.Away
		betSide = SideAway

	case FavoriteAway:
		offers = p.Handicap.Home
		betSide = SideHome

	default:
		return "условие 'фаворит в матче' не выполнено", nil, Unwatch
	}

	// Масимальные плюсовые форы первыми
	SortOffersByParamMaxMin(offers)

	// +2.5
	minPlusHandicap := 2.4

	for _, offer := range offers {
		if offer.Price < 1.5 || offer.Price > 1.7 {
			continue
		}

		if offer.ParamFloat > minPlusHandicap {

			var sideName string

			switch betSide {
			case SideHome:
				sideName = match.Home.CanonicalName

			case SideAway:
				sideName = match.Away.CanonicalName
			}

			var additionalHandicaps string

			if offer.ParamFloat < 3.6 {
				// +3.5, +2.5
				additionalHandicaps = fmt.Sprintf("и выше (+%.1f; +%.1f)",
					offer.ParamFloat+1, offer.ParamFloat+2)
			} else if offer.ParamFloat < 4.6 {
				// // +4.5, +4.0
				additionalHandicaps = fmt.Sprintf("и выше (+%.1f)",
					offer.ParamFloat+1)
			}

			telegramMessage := fmt.Sprintf(`Сигнал # %%d.
Алгоритм %d. Плюсовая фора во 2м сете.
%s
%s - %s

Точка входа +%.1f кф. %.3f

Рекомендация: %s +%.1f %s`,
				s.id,
				match.Champ.Name,
				match.Home.CanonicalName, match.Away.CanonicalName,
				offer.ParamFloat, offer.Price,
				sideName, offer.ParamFloat, additionalHandicaps)

			return "", &BetDetails{
				Market:          MarketHandicap,
				Side:            betSide,
				Param:           offer.Param,
				Price:           offer.Price,
				TelegramMessage: telegramMessage,
			}, Bet
		}
	}

	msg := fmt.Sprintf("не найдена фора +%.1f и выше с коэф. от 1.5 до 1.7", 2.5)

	return msg, nil, Waiting

}
func (s Tennis2ndSetPlusHandicapAny) GetResult(tip model.Tip, result onexbet.TeamMatchResult) (_ *TipResult) {
	score, ok := result.ScoreByPeriods[2]
	if !ok {
		// Сперва проверяем что счет есть.
		return
	}

	// Проверяем что сет завершен
	if !isTennisSetCompleted(score) {
		return
	}

	var handicap float64

	switch tip.Side {
	case SideHome:
		handicap = float64(score.AwayPoints - score.HomePoints)

	case SideAway:
		handicap = float64(score.HomePoints - score.AwayPoints)
	}

	param, err := strconv.ParseFloat(tip.Param, 64)
	if err != nil {
		fmt.Printf("Parse tip param: %s; param=%s", err, tip.Param)
		return
	}

	resultText := fmt.Sprintf(`2й сет: %d-%d`, score.HomePoints, score.AwayPoints)

	if isFloatEqual(param, handicap, 0.1) {
		// Совпасть могут только целые форы +3, +4
		return &TipResult{
			Status: model.Void,
			Result: resultText,
		}
	}

	// Если форы не равны, то одна из них больше на 0.5 минимум, поэтому можно
	// просто сравнить

	if handicap < 0.1 {
		// если меньше нуля (наш теннисит выиграл 2й сет)
		return &TipResult{
			Status: model.Won,
			Result: resultText,
		}
	}

	if handicap < param {
		return &TipResult{
			Status: model.Won,
			Result: resultText,
		}
	} else {
		return &TipResult{
			Status: model.Lost,
			Result: resultText,
		}
	}
}

func (s Tennis2ndSetPlusHandicapAny) GetTelegramTipPattern() string {
	return ""
}

////////////////////////////////////////

// 4 Алгоритм
// плюсовая фора на Фаворита

// Фаорит кф 1,6 и ниже проигрывает первый сет с большим преимуществом
// 0:6
// 1:6
// 2:6

// Во втором сете мы ставим на плюсовую фору фаворита
// +2.5
// +3.5
// Коэф. в диапазоне 1.5 - 2.5

type Tennis2ndSetFavoriteHandicap struct {
	id               int64
	notifyInTelegram bool
	isChampAccepted  func(string) bool
}

func NewTennis2ndSetFavoriteHandicap(opt StrategyOptions) Tennis2ndSetFavoriteHandicap {
	if opt.ID == 0 {
		panic("StrategyID not defined")
	}
	s := Tennis2ndSetFavoriteHandicap{}
	s.id = opt.ID
	s.notifyInTelegram = opt.NotifyInTelegram
	s.isChampAccepted = opt.IsChampAccepted
	return s
}

func (s Tennis2ndSetFavoriteHandicap) ID() int64 {
	return s.id
}

func (s Tennis2ndSetFavoriteHandicap) ShortName() string {
	return fmt.Sprintf("#%d S2 Favorite Handicap", s.id)
}

func (s Tennis2ndSetFavoriteHandicap) IsNotifyInTelegram() bool {
	return s.notifyInTelegram
}

func (s Tennis2ndSetFavoriteHandicap) IsNeedToWatch(liveMatch onexbet.LiveMatch) bool {
	if liveMatch.CurrentPeriod == 1 || liveMatch.CurrentPeriod == 2 {
		return true
	}
	return false
}

func (s Tennis2ndSetFavoriteHandicap) IsInteresting(match *TeamMatch) (interface{}, bool) {
	// Поиск фаворита
	for _, offer := range match.Odds.Winner2Way {
		if offer.Home <= 1.6 {
			match.Lte16FavoriteSide = FavoriteHome

			return fmt.Sprintf("фаворит %s, коэф. %.2f",
				match.Home.CanonicalName, offer.Home), true
		} else if offer.Away <= 1.6 {
			match.Lte16FavoriteSide = FavoriteAway

			return fmt.Sprintf("фаворит %s, коэф. %.2f",
				match.Away.CanonicalName, offer.Away), true
		}
	}
	return nil, false
}
func (s Tennis2ndSetFavoriteHandicap) GetTip(match *TeamMatch, upd onexbet.TeamLiveMatchData) (string, *BetDetails, TipCode) {
	if upd.CurrentPeriod < 2 {
		return "не 2й сет", nil, Waiting
	}

	if upd.CurrentPeriod > 2 {
		return "2й сет завершен", nil, Unwatch
	}

	s1score, ok := upd.ScoreByPeriods[1]
	if !ok {
		// нет счета 1 сета
		return "счет 1го сета не найден", nil, Waiting
	}

	var dif int

	switch match.Lte16FavoriteSide {
	case FavoriteHome:
		dif = s1score.Away - s1score.Home

	case FavoriteAway:
		dif = s1score.Home - s1score.Away
	}

	// фаворит должен проиграть со счетом 6-0, 6-1 или 6-2
	if dif < 4 {
		return fmt.Sprintf("фора первого сета %d", dif), nil, Unwatch
	}

	s2, ok := upd.Periods[2]
	if !ok {
		return "ставки на 2й сет не найдены", nil, Waiting
	}

	// Находим список фор на аутсайдера
	var (
		offers   []onexbet.ParamOffer
		betSide  int
		sideName string
	)

	switch match.Lte16FavoriteSide {
	case FavoriteHome:
		offers = s2.Handicap.Home
		betSide = SideHome
		sideName = match.Home.CanonicalName

	case FavoriteAway:
		offers = s2.Handicap.Away
		betSide = SideAway
		sideName = match.Away.CanonicalName
	}

	// Масимальные плюсовые форы первыми
	SortOffersByParamMaxMin(offers)

	for _, offer := range offers {
		if offer.Price < 1.5 || offer.Price > 2.5 {
			continue
		}

		switch offer.Param {
		case "2.5", "3.5":
			additionalHandicaps := fmt.Sprintf("и выше (+%.1f; +%.1f)",
				offer.ParamFloat+1, offer.ParamFloat+2)

			telegramMessage := fmt.Sprintf(`Сигнал # %%d.
Алгоритм %d. Плюсовая фора во 2м сете.
%s
%s - %s

Точка входа +%.1f кф. %.3f

Рекомендация: %s +%.1f %s`,
				s.id,
				match.Champ.Name,
				match.Home.CanonicalName, match.Away.CanonicalName,
				offer.ParamFloat, offer.Price,
				sideName, offer.ParamFloat, additionalHandicaps)

			return "", &BetDetails{
				Market:          MarketHandicap,
				Side:            betSide,
				Param:           offer.Param,
				Price:           offer.Price,
				TelegramMessage: telegramMessage,
			}, Bet
		}
	}

	msg := fmt.Sprintf("не найдена фора +2.5 / +3.5  с коэф. от 1.5 до 2.5")

	return msg, nil, Waiting

}

// Общий код всех фор - можно выделить в отдельную функцию
func (s Tennis2ndSetFavoriteHandicap) GetResult(tip model.Tip, result onexbet.TeamMatchResult) (_ *TipResult) {
	score, ok := result.ScoreByPeriods[2]
	if !ok {
		// Сперва проверяем что счет есть.
		return
	}

	// Проверяем что сет завершен
	if !isTennisSetCompleted(score) {
		return
	}

	var handicap float64

	switch tip.Side {
	case SideHome:
		handicap = float64(score.AwayPoints - score.HomePoints)

	case SideAway:
		handicap = float64(score.HomePoints - score.AwayPoints)
	}

	param, err := strconv.ParseFloat(tip.Param, 64)
	if err != nil {
		fmt.Printf("Parse tip param: %s; param=%s", err, tip.Param)
		return
	}

	resultText := fmt.Sprintf(`2й сет: %d-%d`, score.HomePoints, score.AwayPoints)

	if isFloatEqual(param, handicap, 0.1) {
		// Совпасть могут только целые форы +3, +4
		return &TipResult{
			Status: model.Void,
			Result: resultText,
		}
	}

	// Если форы не равны, то одна из них больше на 0.5 минимум, поэтому можно
	// просто сравнить

	if handicap < 0.1 {
		// если меньше нуля (наш теннисит выиграл 2й сет)
		return &TipResult{
			Status: model.Won,
			Result: resultText,
		}
	}

	if handicap < param {
		return &TipResult{
			Status: model.Won,
			Result: resultText,
		}
	} else {
		return &TipResult{
			Status: model.Lost,
			Result: resultText,
		}
	}
}

func (s Tennis2ndSetFavoriteHandicap) GetTelegramTipPattern() string {
	return ""
}
