// package warbot -- бот для игры в wartank
package warbot

import (
	"context"
	"fmt"
	"log"
	"strings"
	"time"
	"wartank/pkg/alias"
	"wartank/pkg/components/safe_bool"
	"wartank/pkg/types"
	"wartank/server/serv_bots/warbot/angar"
	"wartank/server/serv_bots/warbot/stat_tank"
	"wartank/server/serv_bots/warbot/warbot_config"
	"wartank/server/serv_bots/warbot/warbot_net"
)

// ВарБот -- бот для игры в вартанк
type ВарБот struct {
	сервер         types.ИСервер
	store          types.ИХранилище
	стата          types.ИТанкСтат
	errFinal       error // Финальная ошибка работы, если была
	ангар          types.ИАнгар
	сеть           types.ИБотСеть
	еслиРаботает   *safe_bool.БезопБул
	еслиАвтозапуск *safe_bool.БезопБул
	конфиг         *warbot_config.ВарБотКонфиг // Конфиг бота для хранения в базе
	кнт            context.Context             // Контекст бота
	фтОтмена       func()                      // Функция отменя контекста бота
}

// ЗагрузитьВарБот -- загружает бота из базы
func ЗагрузитьВарБот(сервер types.ИСервер, номер alias.БотНомер) (types.ИБот, error) {
	{ // Предусловия
		if сервер == nil {
			return nil, fmt.Errorf("ЗагрузитьВарБот(): IApp is nil")
		}
		if номер == 0 {
			return nil, fmt.Errorf("ЗагрузитьВарБот(): номер пустой")
		}
	}
	стрНомер := fmt.Sprint(номер)
	log.Printf("ЗагрузитьВарБот(): номер=%q\n", стрНомер)
	store := сервер.Хранилище()
	binData, err := store.Получ("/bots/" + стрНомер)
	if err != nil {
		if !strings.Contains(err.Error(), "not found") {
			return nil, fmt.Errorf("ЗагрузитьВарБот(): in load bot '%v' from store, err=\n\t%w", номер, err)
		}
		return nil, fmt.Errorf("ЗагрузитьВарБот(): bot '%v' not found in store", номер)
	}
	конфиг := &warbot_config.ВарБотКонфиг{}
	if err = конфиг.Unmarshal(binData); err != nil {
		return nil, fmt.Errorf("ЗагрузитьВарБот(): in unmarshal WarBotConfig(%v) from store, err=\n\t%w", номер, err)
	}
	сам, err := создатьЯдроВарБот(сервер, конфиг)
	if err != nil {
		return nil, fmt.Errorf("ЗагрузитьВарБот(): in make core for bot '%v', err=\n\t%w", номер, err)
	}
	go сам.рестарт()
	return сам, nil
}

// Каждые два часа перезапускает себя
func (сам *ВарБот) рестарт() {
	time.Sleep(time.Hour * 2)
	сам.сервер.Отменить()
	log.Printf("ВарБот.рестарт(): бот %q перезагружен\n", сам.конфиг.Логин_)
}

// НовВарБот -- возвращает новый WarBot
func НовВарБот(сервер types.ИСервер, номер alias.БотНомер, логин, пароль string, еслиАвто bool) (types.ИБот, error) {
	{ // Предусловия
		if сервер == nil {
			return nil, fmt.Errorf("НовВарБот(): IApp is nil")
		}
		if логин == "" {
			return nil, fmt.Errorf("НовВарБот(): login is empty")
		}
		if пароль == "" {
			return nil, fmt.Errorf("НовВарБот(): pass is empty")
		}
	}
	log.Printf("НовВарБот(): name=%q\n", логин)
	config := &warbot_config.ВарБотКонфиг{
		ЕслиАвтозапуск_: еслиАвто,
		Логин_:          логин,
		Пароль_:         пароль,
		Номер_:          номер,
	}
	сам, err := создатьЯдроВарБот(сервер, config)
	if err != nil {
		return nil, fmt.Errorf("НовВарБот(): in make core bot %q, err=\n\t%w", логин, err)
	}
	if err := сам.сохр(); err != nil {
		return nil, fmt.Errorf("НовВарБот(): in self save to store, err=\n\t%w", err)
	}
	return сам, nil
}

// Создаёт ядро бота
func создатьЯдроВарБот(серв types.ИСервер, конфиг *warbot_config.ВарБотКонфиг) (*ВарБот, error) {
	{ // Предусловия
		if серв == nil {
			return nil, fmt.Errorf("NewWarBot(): IServer==nil")
		}
		if конфиг == nil {
			return nil, fmt.Errorf("NewWarBot(): WarBotConfig==nil")
		}
	}
	ctx, fnCancel := context.WithCancel(серв.Контекст())
	сам := &ВарБот{
		сервер:         серв,
		store:          серв.Хранилище(),
		стата:          stat_tank.НовТанкСтат(),
		еслиРаботает:   safe_bool.НовБезопБул(),
		еслиАвтозапуск: safe_bool.НовБезопБул(),
		конфиг:         конфиг,
		кнт:            ctx,
		фтОтмена:       fnCancel,
	}
	var err error
	// WarBotNet
	сам.сеть = warbot_net.НовВарБотСеть(сам)
	сам.ангар, err = angar.НовАнгар(сам)
	if err != nil {
		return nil, fmt.Errorf("NewWarBot(): bot(%q) in make IAngar, err=\n\t%w", сам.Имя(), err)
	}
	if сам.конфиг.ЕслиАвтозапуск_ {
		сам.еслиАвтозапуск.Уст()
		сам.Пуск()
	}
	return сам, nil
}

// Сервер -- возвращает ссылку на объект сервера
func (сам *ВарБот) Сервер() types.ИСервер {
	return сам.сервер
}

// ЕслиПуск -- возвращает признак, что бот подключен
func (сам *ВарБот) ЕслиПуск() bool {
	return сам.еслиРаботает.Получ()
}

// Номер -- возвращает номер бота
func (сам *ВарБот) Номер() alias.БотНомер {
	return сам.конфиг.Номер()
}

// Имя -- возвращает имя бота
func (сам *ВарБот) Имя() string {
	return сам.конфиг.Логин()
}

// Пароль -- возвращает пароль бота
func (сам *ВарБот) Пароль() string {
	return сам.конфиг.Пароль_
}

// Пуск -- запускает бот в работу
func (сам *ВарБот) Пуск() {
	if сам.еслиРаботает.Получ() {
		return
	}
	сам.ангар.Пуск()
	сам.еслиРаботает.Уст()
}

// Error -- возвращает финальную ошибку работы, если была
func (сам *ВарБот) Error() error {
	return сам.errFinal
}

// Ангар -- возвращает ангар игры
func (сам *ВарБот) Ангар() types.ИАнгар {
	return сам.ангар
}

// Стата -- возвращает статистику танка
func (сам *ВарБот) Стата() types.ИТанкСтат {
	return сам.стата
}

// Сеть -- возвращает ссылку на свой сетевой клиент
func (сам *ВарБот) Сеть() types.ИБотСеть {
	return сам.сеть
}

// АвтоИграЕсли -- возвращает признак автоматического запуска бота
func (сам *ВарБот) АвтоИграЕсли() bool {
	return сам.еслиАвтозапуск.Получ()
}

// АвтоИграУст -- устанавливает признак автоматического запуска бота
func (сам *ВарБот) АвтоИграУст() {
	log.Printf("WarBot.SetAutoGame()")
	сам.еслиАвтозапуск.Уст()
	сам.конфиг.ЕслиАвтозапуск_ = true
	сам.сохрКонфиг()
}

// АвтоИграСброс -- сбрасывает признак автоматического запуска бота
func (сам *ВарБот) АвтоИграСброс() {
	log.Printf("WarBot.ResetAutoGame()")
	сам.еслиАвтозапуск.Уст()
	сам.конфиг.ЕслиАвтозапуск_ = false
	сам.сохрКонфиг()
}

// Сохраняет конфиг бота
func (сам *ВарБот) сохрКонфиг() {
	log.Printf("ВарБот.сохрКонфиг()")
	strConf := сам.конфиг.Marshall()
	err := сам.store.Уст("/bots/"+сам.Имя(), strConf)
	if err != nil {
		log.Printf("ВарБот.сохрКонфиг(): err=\n\t%v\n", err)
	}
}

// Контекст -- возвращает контекст бота
func (сам *ВарБот) Контекст() context.Context {
	return сам.кнт
}

// Закончить -- отменяет контекст бота
func (сам *ВарБот) Закончить() {
	сам.фтОтмена()
}

// Сохраняет себя в базу
func (сам *ВарБот) сохр() error {
	стрНомер := fmt.Sprint(сам.Номер())
	err := сам.store.Уст("/bots/"+стрНомер, сам.конфиг.Marshall())
	if err != nil {
		return fmt.Errorf("WarBot.save(): in self save to store bot(%q), err=\n\t%w", сам.Имя(), err)
	}
	return nil
}
