package health

import (
	"context"
	"fmt"

	// "log"
	"strconv"
	"strings"
	"time"

	"wartank/server/serv_bots/warbot/angar/division/divwar/divwaron/health/healthtime"
	"wartank/server/serv_bots/warbot/angar/division/divwar/divwaron/health/repairtime"

	// "wartank/internal/components/sound"
	"wartank/pkg/components/safebool"
	"wartank/pkg/types"
)

/*
	Контролирует состояние здоровья танка
*/

// Health -- контроль здоровья танка
type Health struct {
	types.ИДивизияВойнаДействие // FIXME:
	fnCancel                    func()
	temp                        *healthtime.HealthTime // Изменяемое здоровье танка
	full                        *healthtime.HealthTime // Полное здоровье танка
	isRepair                    *safebool.SafeBool     // Необходимость восстановления
	repairTime                  *repairtime.RepairTime // Время до восстановления
	isEnd                       *safebool.SafeBool     // Ссылка на признак конца сражения
	login                       string                 // Для поиска контрольных строк
	chTick                      chan int               // Канал для ровной отправки тиков
	deltaOld                    int                    // Старая дельта потери здоровья
	countLow                    int
	ctxBattle                   context.Context // Конекст сражения
}

// NewHealth -- возвращает новый *Health
func NewHealth(divwar types.ИДивизияВойнаДействие, isEnd *safebool.SafeBool, login string) (*Health, error) {
	{ // Предусловия
		if divwar == nil {
			return nil, fmt.Errorf("NewHealth(): battle is nil")
		}
		if isEnd == nil {
			return nil, fmt.Errorf("NewHealth(): isEnd is nil")
		}
		if login == "" {
			return nil, fmt.Errorf("NewHealth(): login is empty")
		}
	}
	sf := &Health{
		ИДивизияВойнаДействие: divwar,
		fnCancel:   divwar.CancelBattle,
		ctxBattle:  divwar.Ctx(),
		temp:       healthtime.NewHealthTime(),
		full:       healthtime.NewHealthTime(),
		isRepair:   safebool.NewSafeBool(),
		repairTime: repairtime.NewRepairTime(),
		isEnd:      divwar.ЕслиКонец(),
		login:      login,
		chTick:     make(chan int, 2),
	}
	go sf.makeTik()
	go sf.run()
	return sf, nil
}

// Отправляе ттики с заданным равным интервалом
func (sf *Health) makeTik() {
	defer func() {
		sf.CancelBattle()
		close(sf.chTick)
		// log._rintf("Health.makeTick(): сражение завершёно\n")
	}()
	count := 0
	repairTime := 0
	for {
		select {
		case <-sf.ctxBattle.Done():
			return
		default:
			if sf.IsDeath() {
				return
			}
			if sf.repairTime.Get() == repairTime {
				count++
			} else {
				repairTime = sf.repairTime.Get()
				count = 0
			}
			if count > 90 {
				return
			}
		}
		sf.chTick <- 1
		time.Sleep(time.Second * 1)
		sf.repairTime.Dec()
	}
}

// Главный цикл обработки здоровья в сражении
func (sf *Health) run() {
	for {
		select {
		case <-sf.ctxBattle.Done():
			sf.isEnd.Уст()
			return
		case <-sf.chTick:
			if err := sf.findHealth(); err != nil { // Найти свой здоровье
				// log._rintf("ERRO Health.run(): при попытке найти здоровье, err=\n\t%v\n", err)
			}
			sf.findRepairTime()
			if sf.ВыстрелБлок().Получ() {
				if sf.isRepair.Получ() {
					go sf.repair()
				}
				continue
			}
			if sf.isRepair.Получ() {
				go sf.repair()
			}
		}
	}
}

// Full -- возвращает объект полного здоровья танка
func (sf *Health) Full() int {
	return sf.full.Get()
}

// IsDeath -- возвращает признак мертвичины танка
func (sf *Health) IsDeath() bool {
	if sf.isEnd.Получ() {
		sf.fnCancel()
		return true
	}
	lstBattle := sf.СписПолучить()
	for _, strOut := range lstBattle {
		if strings.Contains(strOut, `>Ваш танк подбит.`) {
			// log._rintf("INFO Health.repair(): танк подбит\n")
			sf.temp.Set(0)
			sf.isEnd.Уст()
			sf.CancelBattle()
			return true
		}
	}
	return sf.isEnd.Получ()
}

// Ищет время восстановления ремки
func (sf *Health) findRepairTime() {
	defer func() {
		if sf.repairTime.IsReady() {
			return
		}
		if sf.repairTime.IsChange() {
			// log._rintf("INFO Health.findRepair(): до ремки=%v\n", sf.repairTime.Get())
		}
	}()
	if sf.repairTime.IsReady() {
		return
	}
	var (
		strOut    string
		lstBattle = sf.СписПолучить()
		isFind    bool
		ind       int
	)
	// <a href="pve?19-14.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>12 секунд</span></span></a>
	//
	for ind, strOut = range lstBattle {
		if !strings.Contains(strOut, `ILinkListener-currentControl-repairLink`) {
			continue
		}
		if strings.Contains(strOut, ` секунд</span></span></a>`) {
			isFind = true
			break
		}
	}
	if !isFind {
		return
	}
	strOut = lstBattle[ind]
	// <a href="pve?19-14.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>12 секунд</span></span></a>
	lstTime := strings.Split(strOut, `ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>`)
	if len(lstTime) < 2 {
		// log._rintf("ERRO Health.findRepair(): при попытке получить ссылку на ремонт, strOut=\n%v\n", strOut)
		sf.isEnd.Уст()
		sf.CancelBattle()
		return
	}
	strTime := lstTime[1]
	lstTime = strings.Split(strTime, ` секунд</span></span></a>`)
	strTime = lstTime[0]
	if err := sf.repairTime.Set(strTime); err != nil {
		// log._rintf("ERRO Health.findRepair(): при установке времени восстановления ремки, err=\n\t%v\n", err)
	}
}

// Восстановливает здоровье (~)
func (sf *Health) repair() {
	var (
		strOut       string
		lstBattleOn  = sf.СписПолучить()
		isFindRepair bool
		ind          int
	)
	// <span>Ремкомплект</span>
	// <a href="pve?19-14.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>Ремкомплект</span></span></a>
	for ind, strOut = range lstBattleOn {
		if strings.Contains(strOut, `<span>Ремкомплект</span>`) {
			isFindRepair = true
			break
		}
	}
	if !isFindRepair {
		return
	}
	strOut = lstBattleOn[ind]
	// <a href="pve?6-26.ILinkListener-currentControl-repairLink" class="simple-but blue"><span><span>Ремкомплект</span></span></a>
	lstLink := strings.Split(strOut, `<a href="`)
	strLink := lstLink[1]
	lstLink = strings.Split(strLink, `" class="simple-but blue"><span><span>Ремкомплект</span></span></a>`)
	strLink = "https://wartank.ru/" + lstLink[0]
	lstBattleOn, err := sf.Сеть().Get(strLink)
	if err != nil {
		// log._rintf("ERRO Health.repair(): при выполнении GET-команды ремонта, err=\n\t%v\n", err)
		sf.isEnd.Уст()
		sf.CancelBattle()
		return
	}
	if err = sf.СтрОбновить(lstBattleOn); err != nil {
		// log._rintf("ERRO Health.repair(): при обновлении lstBattle, err=\n\t%v\n", err)
		sf.isEnd.Уст()
		sf.CancelBattle()
		return
	}
	// sound.Repair()
	// log._rintf("INFO Health.repair(): здоровье восстановлено\n")
}

// Ищет своё здоровье (~)
func (sf *Health) findHealth() error {
	var (
		ind       int
		strOut    string
		isFind    bool
		lstBattle = sf.СписПолучить()
	)
	if len(lstBattle) == 0 { // Принудительно обновим сражение
		if err := sf.Сеть().UpdateLst(); err != nil {
			sf.isEnd.Уст()
			sf.fnCancel()
			return fmt.Errorf("Health.findHealth(): после принудительного обновления lsBattleOn, err=\n\t%w", err)
		}
	}
	for ind, strOut = range lstBattle {
		if strings.Contains(strOut, `alt="`+sf.login+`"`) {
			isFind = true
			break
		}
	}
	if !isFind { // Свой танк не найден
		sf.isEnd.Уст()
		sf.fnCancel()
		return fmt.Errorf("Health.findHealth(): своё здоровье не найдено")
	}
	// Свой танк найден, ищем здоровье
	ind += 11
	strOut = lstBattle[ind]
	lstHealth := strings.Split(strOut, `<div class="value-block lh1"><span><span>`)
	strHealth := lstHealth[1]
	lstHealth = strings.Split(strHealth, `</span></span></div>`)
	strHealth = lstHealth[0]
	iHealth, err := strconv.Atoi(strHealth)
	if err != nil {
		sf.isEnd.Уст()
		sf.CancelBattle()
		return fmt.Errorf("Health.findHealth(): здоровье(%v) не число, err=%w", strHealth, err)
	}
	sf.setHealth(iHealth)
	return nil
}

// setHealth -- устанавливает текущее здоровье
func (sf *Health) setHealth(val int) {
	if val < 0 {
		// log._rintf("WARN Health.setHealth(): кривое значение здоровья танка(%v)\n", val)
		val = 0
	}

	if val > sf.full.Get() {
		// log._rintf("WARN Health.setHealth(): кривое текущее здоровье, %v/%v\n", val, sf.full.Get())
		sf.full.Set(val)
		sf.temp.Set(val)
		sf.deltaOld = 0
		sf.ВыстрелБлок().Сброс()
		sf.isRepair.Сброс()
		return
	}

	delta := sf.temp.Get() - val
	if delta > 0 { // Дельта будет больше нуля, если только
		if delta != sf.deltaOld {
			// log._rintf("INFO Health.setHealth(): потеря здоровья=%v/%v\n", -delta, val)
			sf.deltaOld = delta
			sf.temp.Set(val)
		}
	}

	switch {
	case sf.isEnd.Получ():
		sf.temp.Set(0)
		sf.isEnd.Уст()
		sf.CancelBattle()
		return
	case val == 0:
		sf.temp.Set(0)
		sf.isEnd.Уст()
		sf.CancelBattle()
		return
	case val <= 500: // Запретить стрельбу
		sf.ВыстрелБлок().Уст() // Установить запрет стрельбы пока слабое здоровье
		sf.isRepair.Уст()
		// log._rintf("WARN Health.setHealth(): низкий уровень здоровья(%v)\n", val)
		sf.Манёвр()
	case val > 500: // Разрешить стрельбы
		sf.ВыстрелБлок().Сброс()
		sf.isRepair.Сброс()
		if delta > sf.full.Get()*4/10 { // Проверить на критичность падения здоровья на 40%
			// log._rintf("WARN Health.setHealth(): большая разовая потеря здоровья(%v)\n", delta)
			sf.Манёвр()
			sf.isRepair.Уст()
			return
		}
	}

	isMask := sf.ВыстрелБлок().Получ()
	switch isMask {
	case true:
		sf.countLow++
		if sf.countLow >= 200 {
			sf.isEnd.Уст()
			sf.CancelBattle()
			return
		}
	default:
		sf.countLow = 0
	}
	if val == sf.full.Get() {
		sf.temp.Set(val)
		sf.isRepair.Сброс()
		sf.ВыстрелБлок().Сброс()
		sf.countLow = 0
	}
}
