package netclient

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"strings"
	"sync"
	"time"

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

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

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

// NetClient -- объект сетевого соединения
type NetClient struct {
	botNet types.ИБотСеть
	conn   *http.Client
	stat   *netstat.NetStat
	chRes  chan *response // Канал ответа от http-клиента
	block  sync.Mutex
}

// NewNetClient -- возвращает сетевого клиента
func NewNetClient(botNet types.ИБотСеть) *NetClient {
	sf := &NetClient{
		botNet: botNet,
		conn:   botNet.Коннект(),
		stat:   netstat.NewNetStat(botNet),
		chRes:  make(chan *response, 2),
	}
	return sf
}

// Get -- выполняет безопасный GET-запрос в сеть
func (sf *NetClient) Get(strLink string) (lstString []string, err error) {
	sf.block.Lock()
	defer sf.block.Unlock()
	ctxCancel, fnCancel := context.WithTimeout(sf.botNet.Кнт(), time.Second*7)
	defer fnCancel()
	defer func() { // Возможный перехват паники
		if _panic := recover(); _panic != nil {
			err = fmt.Errorf("NetClient.Get().defer(): перехвачена паника для 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.botNet.Отмена()
		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) {
	resp := &response{}
	defer func() {
		if resp.err != nil {
			sf.stat.IncErr()
		}
	}()
	req, err := http.NewRequest("GET", strLink, nil)
	if err != nil {
		resp.err = fmt.Errorf("NetClient.get(): при создании запроса, err=\n\t%w", err)
		return
	}
	req.Header.Set("User-Agent", "Mozilla Firefox 94.1")
	httpResp, err := sf.conn.Do(req)
	if err != nil {
		resp.err = fmt.Errorf("NetClient.get(): при выполнении GET-запроса, err=\n\t%w", err)
		return
	}
	defer sf.closeGetBody(strLink, httpResp, resp)

	if httpResp.StatusCode != http.StatusOK {
		resp.err = fmt.Errorf("NetClient.get(): code=%v, status=%v", httpResp.StatusCode, httpResp.Status)
		return
	}
	binData, err := io.ReadAll(httpResp.Body)
	if err != nil {
		resp.err = fmt.Errorf("NetClient.get(): при чтении тела ответа, err=\n\t%w", err)
		return
	}
	if len(binData) == 0 {
		resp.err = fmt.Errorf("NetClient.get(): пустое тело ответа, err=\n\t%w", err)
		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(): lstString is empty")
		return
	}
	resp.lstString = lstString
}

// Вызывается по завершению вызова, закрывает тело запроса
func (sf *NetClient) closeGetBody(strLink string, httpResp *http.Response, resp *response) {
	defer func() {
		if _panic := recover(); _panic != nil {
			// log._rintf("NetClient.closeGetBody(): strLink='%v', panic=%v\n", strLink, _panic)
			sf.botNet.Отмена()
		}
	}()
	err := httpResp.Body.Close()
	if err != nil {
		_err := fmt.Errorf("NetClient.closeGetBody(): ошибка при закрытии запроса URL(%q), err=\n\t%w", strLink, err)
		if resp.err != nil { // Есть и ошибка в закрытии тела запроса и внутренняя
			resp.err = fmt.Errorf("NetClient.closeGetBody(): двойная ошибка при закрытии запроса URL(%q), err=\n\t%w\n\tinternal err=\n\t%w", strLink, _err, resp.err)
		}
		resp.lstString = nil
		sf.stat.IncErr()
	}
	sf.chRes <- resp
}
