package netclient

import (
	"context"
	"fmt"
	"io"
	"log"

	// "log"
	"net/http"
	"strings"
	"sync"
	"time"

	"wartank/pkg/components/sectionnet/netstat"
	"wartank/pkg/types"
)

/*
	Объект сетевого соединения
*/

// Ответ после запроса
type response struct {
	lstString []string
	err       error
}

// NetClient -- объект сетевого соединения
type NetClient struct {
	server   types.IServer
	conn     *http.Client
	stat     *netstat.NetStat
	ctx      context.Context
	chRes    chan *response // Канал ответа от http-клиента
	fnCancel func()         // Отмена контекста приложения
	block    sync.Mutex
}

// NewNetClient -- возвращает сетевого клиента
func NewNetClient(server types.IServer, bot types.IServBot) *NetClient {
	sf := &NetClient{
		server:   server,
		conn:     bot.BotNet().Conn(),
		stat:     netstat.NewNetStat(server),
		chRes:    make(chan *response, 2),
		ctx:      context.Background(),
		fnCancel: server.CancelApp,
	}
	return sf
}

// Теневая функция на блокировку
func (sf *NetClient) Get(strLink string) (lstString []string, err error) {
	sf.block.Lock()
	defer sf.block.Unlock()
	ctxCancel, fnCancel := context.WithTimeout(sf.ctx, time.Second*7)
	defer func() {
		fnCancel()
		if _panic := recover(); _panic != nil {
			err = fmt.Errorf("NetClient.get(): перехвачена паника для URL(%v), panic=%v", strLink, _panic)
			lstString = nil
			time.Sleep(time.Millisecond * 250) // Чтобы не насиловать не работающую сеть
		}
	}()
	go sf.get(strLink)
	select {
	case <-ctxCancel.Done(): // Таймаут по ожиданию
		err = fmt.Errorf("NetClient.get(): таймаут ожидания ответа")
		sf.fnCancel()
		return nil, err
	case resp := <-sf.chRes: // Получен ответ
		if resp.err != nil {
			return nil, resp.err
		}
		return resp.lstString, nil
	}
}

// Внутренний вызов для сокрытия под общей блокировкой
func (sf *NetClient) get(strLink string) {
	req, err := http.NewRequest("GET", strLink, nil)
	if err != nil {
		log.Fatalln(err)
	}
	req.Header.Set("User-Agent", "Mozilla Firefox 86.0")
	httpResp, err := sf.conn.Do(req)
	resp := &response{}
	if err != nil {
		resp.err = fmt.Errorf("NetClient.get().fnGet(): при выполнении GET-запроса, err=\n\t%w", err)
		sf.stat.AddByte(-1) // Фиксируем ошибку
		return
	}
	defer sf.endGet(strLink, httpResp, resp)

	if httpResp.StatusCode != http.StatusOK {
		resp.err = fmt.Errorf("NetClient.get().fnGet(): code=%v, status=%v", httpResp.StatusCode, httpResp.Status)
		sf.stat.AddByte(-1) // Фиксируем ошибку
		return
	}
	binData, err := io.ReadAll(httpResp.Body)
	if err != nil {
		resp.err = fmt.Errorf("NetClient.get().fnGet(): при чтении тела ответа, err=\n\t%w", err)
		sf.stat.AddByte(-1) // Фиксируем ошибку
		return
	}
	if len(binData) == 0 {
		resp.err = fmt.Errorf("NetClient.get().fnGet(): пустое тело ответа, err=\n\t%w", err)
		sf.stat.AddByte(-1) // Фиксируем ошибку
		return
	}
	lenData := len(binData) + len(strLink)
	sf.stat.AddByte(lenData)

	lstString := strings.Split(string(binData), "\n")
	if len(lstString) == 0 {
		resp.err = fmt.Errorf("NetClient.get().fnGet(): lstString is empty")
		sf.stat.AddByte(-1) // Фиксируем ошибку
		return
	}
	resp.lstString = lstString
}

// Вызывается по завершению вызова
func (sf *NetClient) endGet(strLink string, httpResp *http.Response, resp *response) {
	defer func() {
		if _panic := recover(); _panic != nil {
			// log._rintf("NetClient.sndGet(): strLink='%v', panic=%v\n", strLink, _panic)
			sf.fnCancel()
		}
	}()
	err := httpResp.Body.Close()
	if err != nil {
		switch resp.err == nil {
		case true:
			resp.err = fmt.Errorf("NetClient.get().fnGet(): ошибка при закрытии запроса URL(%v), err=\n\t%w", strLink, err)
		case false:
			resp.err = fmt.Errorf("NetClient.get().fnGet(): ошибка при закрытии запроса URL(%v), err=\n\t%v\n\toldErr=\n\t%w", strLink, err, resp.err)
		}
		resp.lstString = nil

		sf.stat.AddByte(-1) // Отметим ошибку закрытия клиента
	}
	sf.chRes <- resp
}
