package events

import (
	ibase64 "agent/commons/base64"
	"agent/commons/bytes"
	"agent/commons/debug"
	"agent/commons/utils"
	"agent/defines/derrs"
	"agent/defines/dresptype"
	"encoding/json"
	"io"
	"os"

	"github.com/google/uuid"
)

type responseReader struct {
	reader io.Reader
	readed int64

	respType    int
	isErrorData bool
	isEventData bool
	isDataOnly  bool

	data                []byte
	dataFile            string
	rawEvent            *RawEvent
	maxDataSizeInMemory int64
}

func (r *responseReader) ErrorData() ([]byte, bool) {
	if r.isErrorData {
		return r.data, true
	}
	return nil, false
}

func (r *responseReader) IsEvent() bool {
	return r.rawEvent != nil
}

func (r *responseReader) IsEventData() bool {
	return r.isEventData
}

func (r *responseReader) IsDataOnly() bool {
	return r.isDataOnly
}

func (r *responseReader) DataOnly() []byte {
	if r.isDataOnly {
		return r.data
	}
	return nil
}

func (r *responseReader) EventBody() (event *RawEvent) {
	return r.rawEvent
}

func (r *responseReader) EventData() (data []byte) {
	if r.isEventData {
		return r.data
	}
	return nil
}

func (r *responseReader) ResponseType() int {
	return r.respType
}

func (r *responseReader) parse() (err error) {
	defer func() {
		if err != nil {
			err = derrs.Join(err, derrs.NewReadError())
		}
	}()

	dataSize, err := r.parseHeader()
	if err != nil {
		return err
	}

	// Путь к файлу инициализируется только в том случае, если данные были получены
	dataFile := r.dataFile
	r.dataFile = ""

	if r.respType == dresptype.Error || r.respType == dresptype.Event || r.respType == dresptype.EventBody {
		if dataSize > 0 {
			r.data = make([]byte, dataSize)
			if _, err = r._read(r.data); err != nil {
				return err
			}
		}
		if r.respType == dresptype.Error {
			r.isErrorData = true
			return nil
		}
	} else if r.respType == dresptype.Data {
		if err = r._parseData(dataSize, dataFile); err != nil {
			return err
		}
		r.isDataOnly = true
		return nil
	} else {
		return derrs.NewUnknownHeaderTypeError(`invalid response type`)
	}

	if len(r.data) == 0 {
		return derrs.NewUnknownError(`no data in response`)
	}

	r.rawEvent = &RawEvent{}
	if err = json.Unmarshal(r.data, r.rawEvent); err != nil {
		return err
	}

	if r.rawEvent.VirtualUuid == uuid.Nil {
		debug.Logf(`event.VirtualUuid is nil`)
	}

	if r.rawEvent.DataSize > 0 {
		if len(r.rawEvent.Data) > 0 {
			if r.data, err = ibase64.DecodeString(r.rawEvent.Data); err != nil {
				return err
			}
		} else if r.respType == dresptype.Event {
			if err = r._parseData(r.rawEvent.DataSize, dataFile); err != nil {
				return err
			}
		} else {
			return derrs.NewDataNotFoundError()
		}
		r.isEventData = true
	}

	return nil
}

func (r *responseReader) parseHeader() (dataSize int64, err error) {
	raw := make([]byte, 1)
	if _, err = r._read(raw); err != nil {
		return 0, err
	}

	widths := raw[0]
	dataSizeWidth := int(bytes.Range(widths, 0, 3))
	respTypeWidth := int(bytes.Range(widths, 3, 2))
	trashWidth := int(bytes.Range(widths, 5, 2))

	header := make([]byte, dataSizeWidth+respTypeWidth+trashWidth)
	if _, err = r._read(header); err != nil {
		return 0, err
	}

	if respTypeWidth == 0 {
		return 0, derrs.NewUnknownError(`respTypeWidth == 0`)
	}

	offset := 0
	if dataSizeWidth > 0 {
		dataSize, _ = bytes.ToNum[int64](header[offset:], dataSizeWidth)
		offset += dataSizeWidth
	}

	if respTypeWidth > 0 {
		r.respType, _ = bytes.ToNum[int](header[offset:], respTypeWidth)
		offset += respTypeWidth
	}

	if trashWidth > 0 {
		trashSize, _ := bytes.ToNum[int64](header[offset:], trashWidth)
		if trashSize > 0 {
			n, err := io.CopyN(io.Discard, r.reader, trashSize)
			if err != nil {
				return 0, err
			}
			r.readed += n
		}
	}

	return dataSize, nil
}

func (r *responseReader) _parseData(dataSize int64, dataFile string) (err error) {
	r.data = nil

	if dataSize == 0 {
		return derrs.NewDataNotFoundError(`dataSize == 0`)
	}

	if len(dataFile) > 0 || (r.maxDataSizeInMemory > 0 && dataSize > r.maxDataSizeInMemory) {
		r.dataFile = dataFile
		if len(r.dataFile) == 0 {
			if r.dataFile, err = r.newDataFile(); err != nil {
				return err
			}
		}
		if readed, err := utils.ReaderToFile(r.reader, r.dataFile, dataSize); err != nil {
			r.readed += readed
			return err
		}

	} else {
		data := make([]byte, dataSize)
		if readed, err := r._read(data); err != nil {
			return err
		} else if readed != dataSize {
			return derrs.NewReadedDataSizeNotEqualDataSizeError(`readed != dataSize`)
		}
		r.data = data
	}
	return nil
}

func (r *responseReader) _read(buff []byte) (readed int64, err error) {
	end := int64(len(buff))
	for n, err := r.reader.Read(buff[readed:]); readed < end && err == nil; {
		readed += int64(n)
	}
	r.readed += readed
	if readed < end {
		return readed, io.ErrUnexpectedEOF
	}
	return readed, nil
}

func (r *responseReader) newDataFile() (string, error) {
	tmpFile, err := os.CreateTemp("", "*")
	if err != nil {
		return "", err
	}
	defer tmpFile.Close()
	return tmpFile.Name(), nil
}
