// package client_anonym -- HTTP-клиент для выполнения анонимных запросов на сервер
package client_anonym

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

	"git.p78su.freemyip.com/svi/gostore/pkg/msg_net"
	"git.p78su.freemyip.com/svi/gostore/pkg/types"
)

// ClientAnonym -- HTTP-клиент для выполнения анонимных запросов на сервер
type ClientAnonym struct {
	servCtx   types.IServCtx
	client    *http.Client
	url       string       // Базовый адрес сервера
	urlTime   string       // URL для получения времени
	urlPut    string       // URL для записи данных
	urlRead   string       // URL для чтения данных
	urlDelete string       // URL для удаления данных
	urlFind   string       // URL для поиска данных
	block     sync.RWMutex // Блокировка проверки работы клиента
	isWork    bool         // Признак работы клиента
}

// NewClientAnonym -- конструктор
func NewClientAnonym(serv types.IServCtx, url string) (*ClientAnonym, error) {
	{ // Предусловия
		if serv == nil {
			return nil, fmt.Errorf("NewClientAnonym(): IServCtx==nil")
		}
		if url == "" {
			return nil, fmt.Errorf("NewClientAnonym(): url is empty")
		}
	}
	sf := &ClientAnonym{
		servCtx: serv,
		client: &http.Client{
			Transport: &http.Transport{
				Proxy: http.ProxyFromEnvironment,
				DialContext: (&net.Dialer{
					Timeout:   5 * time.Second,
					KeepAlive: 30 * time.Second,
				}).DialContext,
				ForceAttemptHTTP2:     false,
				MaxIdleConns:          10,
				IdleConnTimeout:       30 * time.Second,
				TLSHandshakeTimeout:   10 * time.Second,
				ExpectContinueTimeout: 5 * time.Second,
			},
		},
		url:       url,
		urlTime:   url + "anonym/time",
		urlPut:    url + "anonym/put",
		urlRead:   url + "anonym/get",
		urlDelete: url + "anonym/del",
		urlFind:   url + "anonym/find",
	}
	sf.servCtx.Wg().Add(1)
	go sf.close()
	sf.isWork = true
	return sf, nil
}

// Проверка на признак работы клиента
func (sf *ClientAnonym) isClosed() bool {
	sf.block.RLock()
	defer sf.block.RUnlock()
	return !sf.isWork
}

// Find -- ищет ключи по префиксу
func (sf *ClientAnonym) Find(prefixKey string) ([]string, error) {
	if sf.isClosed() {
		return nil, fmt.Errorf("ClientAnonym.Find(): client is closed")
	}
	if prefixKey == "" {
		return nil, fmt.Errorf("ClientAnonym.Find(): prefixKey is empty")
	}
	formPost := url.Values{
		"key": {prefixKey},
	}
	resp, err := sf.client.Post(sf.urlFind, "application/x-www-form-urlencoded", strings.NewReader(formPost.Encode()))
	if err != nil {
		return nil, fmt.Errorf("ClientAnonym.Find(): in get url(%q), err=\n\t%w", sf.urlFind, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("ClientAnonym.Find(): err=%s", resp.Status)
	}
	binBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("ClientAnonym.Find(): in read body, err=\n\t%w", err)
	}
	req, err := msg_net.UnmarshalFindRequest(prefixKey, binBody)
	if err != nil {
		return nil, fmt.Errorf("ClientAnonym.Find(): in unmarshal find request, err=\n\t%w", err)
	}
	return req.Values(), nil
}

// Delete -- удаление ключа из хранилища
func (sf *ClientAnonym) Delete(key string) error {
	if sf.isClosed() {
		return fmt.Errorf("ClientAnonym.Delete(): client is closed")
	}
	if key == "" {
		return fmt.Errorf("ClientAnonym.Delete(): key is empty")
	}
	formPost := url.Values{
		"key": {key},
	}
	resp, err := sf.client.Post(sf.urlDelete, "application/x-www-form-urlencoded", strings.NewReader(formPost.Encode()))
	if err != nil {
		return fmt.Errorf("ClientAnonym.Delete(): in get url(%q), err=\n\t%w", sf.urlDelete, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("ClientAnonym.Delete(): err=%s", resp.Status)
	}
	return nil
}

// Read -- получает значение по ключу
func (sf *ClientAnonym) Read(key string) (string, error) {
	if sf.isClosed() {
		return "", fmt.Errorf("ClientAnonym.Read(): client is closed")
	}
	if key == "" {
		return "", fmt.Errorf("ClientAnonym.Read(): key is empty")
	}
	formPost := url.Values{
		"key": {key},
	}
	resp, err := sf.client.Post(sf.urlRead, "application/x-www-form-urlencoded", strings.NewReader(formPost.Encode()))
	if err != nil {
		return "", fmt.Errorf("ClientAnonym.Read(): in get url(%q), err=\n\t%w", sf.urlRead, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("ClientAnonym.Read(): err=%s", resp.Status)
	}
	binBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("ClientAnonym.Read(): err=\n\t%w", err)
	}
	strResp := string(binBody)
	return strResp, nil
}

// Put -- помещает в хранилище ключ и значение
func (sf *ClientAnonym) Put(key string, binData []byte) (string, error) {
	if sf.isClosed() {
		return "", fmt.Errorf("ClientAnonym.Ping(): client is closed")
	}
	if key == "" {
		return "", fmt.Errorf("ClientAnonym.Put(): key is empty")
	}
	postForm := url.Values{
		"key": {key},
		"val": {string(binData)},
	}
	resp, err := sf.client.Post(sf.urlPut, "application/x-www-form-urlencoded", strings.NewReader(postForm.Encode()))
	if err != nil {
		return "", fmt.Errorf("ClientAnonym.Put(): in get url(%q), err=\n\t%w", sf.url, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("ClientAnonym.Put(): err=%s", resp.Status)
	}
	binBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("ClientAnonym.Put(): err=\n\t%w", err)
	}
	strResp := string(binBody)
	return strResp, nil
}

// Time -- пингует указанный сервис в реальном времени
func (sf *ClientAnonym) Time() error {
	if sf.isClosed() {
		return fmt.Errorf("ClientAnonym.Time(): client is closed")
	}
	resp, err := sf.client.Get(sf.urlTime)
	if err != nil {
		return fmt.Errorf("ClientAnonym.Time(): in get url(%q), err=\n\t%w", sf.urlTime, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("ClientAnonym.Time(): err=%s", resp.Status)
	}
	binBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("ClientAnonym.Time(): err=\n\t%w", err)
	}
	_ = binBody
	return err
}

// Ожидает закрытия сервиса и освобождает сервис
func (sf *ClientAnonym) close() {
	<-sf.servCtx.Ctx().Done()
	sf.block.Lock()
	defer sf.block.Unlock()
	if !sf.isWork {
		return
	}
	sf.isWork = false
	sf.servCtx.Wg().Done()
}
