package gadb

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"net"
	"time"

	"github.com/rs/zerolog/log"
)

type syncTransport struct {
	sock        net.Conn
	readTimeout time.Duration
}

// newSyncTransport creates a new sync transport with existed tcp socket connection
func newSyncTransport(sock net.Conn, readTimeout time.Duration) syncTransport {
	return syncTransport{sock: sock, readTimeout: readTimeout}
}

func (sync syncTransport) Send(command, data string) (err error) {
	if len(command) != 4 {
		return errors.New("sync commands must have length 4")
	}
	msg := bytes.NewBufferString(command)
	if err = binary.Write(msg, binary.LittleEndian, int32(len(data))); err != nil {
		return fmt.Errorf("sync transport write: %w", err)
	}
	msg.WriteString(data)

	log.Debug().Str("msg", msg.String()).Msg("sync run adb command")
	return _send(sync.sock, msg.Bytes())
}

func (sync syncTransport) SendStream(reader io.Reader) (err error) {
	syncMaxChunkSize := 64 * 1024
	for err == nil {
		tmp := make([]byte, syncMaxChunkSize)
		var n int
		n, err = reader.Read(tmp)
		if err == io.EOF {
			err = nil
			break
		}
		if err == nil {
			err = sync.sendChunk(tmp[:n])
		}
	}

	return
}

func (sync syncTransport) SendStatus(statusCode string, n uint32) (err error) {
	msg := bytes.NewBufferString(statusCode)
	if err = binary.Write(msg, binary.LittleEndian, n); err != nil {
		return fmt.Errorf("sync transport write: %w", err)
	}
	log.Debug().Str("msg", msg.String()).Msg("sync send adb status")
	return _send(sync.sock, msg.Bytes())
}

func (sync syncTransport) sendChunk(buffer []byte) (err error) {
	msg := bytes.NewBufferString("DATA")
	if err = binary.Write(msg, binary.LittleEndian, int32(len(buffer))); err != nil {
		return fmt.Errorf("sync transport write: %w", err)
	}
	msg.Write(buffer)
	return _send(sync.sock, msg.Bytes())
}

func (sync syncTransport) VerifyStatus() (err error) {
	var status string
	if status, err = sync.ReadStringN(4); err != nil {
		return err
	}

	logs := bytes.NewBufferString(fmt.Sprintf("<-- %s", status))
	defer func() {
		fmt.Println(logs.String())
	}()

	var tmpUint32 uint32
	if tmpUint32, err = sync.ReadUint32(); err != nil {
		return fmt.Errorf("sync transport read (status): %w", err)
	}
	logs.WriteString(fmt.Sprintf(" %d\t", tmpUint32))

	var msg string
	if msg, err = sync.ReadStringN(int(tmpUint32)); err != nil {
		return err
	}
	logs.WriteString(msg)

	if status == "FAIL" {
		err = fmt.Errorf("sync verify status (fail): %s", msg)
		return
	}

	if status != "OKAY" {
		err = fmt.Errorf("sync verify status: Unknown error: %s", msg)
		return
	}

	return
}

var syncReadChunkDone = errors.New("sync read chunk done")

func (sync syncTransport) WriteStream(dest io.Writer) (err error) {
	var chunk []byte
	save := func() error {
		if chunk, err = sync.readChunk(); err != nil && err != syncReadChunkDone {
			return fmt.Errorf("sync read chunk: %w", err)
		}
		if err == syncReadChunkDone {
			return err
		}
		if err = _send(dest, chunk); err != nil {
			return fmt.Errorf("sync write stream: %w", err)
		}
		return nil
	}

	for err == nil {
		err = save()
	}

	if err == syncReadChunkDone {
		err = nil
	}
	return
}

func (sync syncTransport) readChunk() (chunk []byte, err error) {
	var status string
	if status, err = sync.ReadStringN(4); err != nil {
		return nil, err
	}

	logs := bytes.NewBufferString("")
	defer func() {
		fmt.Println(logs.String())
	}()

	var tmpUint32 uint32
	if tmpUint32, err = sync.ReadUint32(); err != nil {
		return nil, fmt.Errorf("read chunk (length): %w", err)
	}

	if status == "FAIL" {
		logs.WriteString(fmt.Sprintf("<-- %s\t%d\t", status, tmpUint32))
		var sError string
		if sError, err = sync.ReadStringN(int(tmpUint32)); err != nil {
			return nil, fmt.Errorf("read chunk (error message): %w", err)
		}
		err = fmt.Errorf("status (fail): %s", sError)
		logs.WriteString(sError)
		return
	}

	switch status {
	case "DONE":
		logs.WriteString(fmt.Sprintf("<-- %s", status))
		err = syncReadChunkDone
		return
	case "DATA":
		logs.WriteString(fmt.Sprintf("<-- %s\t%d\t", status, tmpUint32))
		if chunk, err = sync.ReadBytesN(int(tmpUint32)); err != nil {
			return nil, err
		}
	default:
		logs.WriteString(fmt.Sprintf("<-- %s\t%d\t", status, tmpUint32))
		err = errors.New("unknown error")
	}

	logs.WriteString("......")

	return
}

func (sync syncTransport) ReadDirectoryEntry() (entry DeviceFileInfo, err error) {
	var status string
	if status, err = sync.ReadStringN(4); err != nil {
		return DeviceFileInfo{}, err
	}

	logs := bytes.NewBufferString(fmt.Sprintf("<-- %s", status))
	defer func() {
		fmt.Println(logs.String())
	}()

	if status == "DONE" {
		return
	}

	logs = bytes.NewBufferString(fmt.Sprintf("<-- %s\t", status))

	if err = binary.Read(sync.sock, binary.LittleEndian, &entry.Mode); err != nil {
		return DeviceFileInfo{}, fmt.Errorf("sync transport read (mode): %w", err)
	}
	logs.WriteString(entry.Mode.String() + "\t")

	if entry.Size, err = sync.ReadUint32(); err != nil {
		return DeviceFileInfo{}, fmt.Errorf("sync transport read (size): %w", err)
	}
	logs.WriteString(fmt.Sprintf("%10d", entry.Size) + "\t")

	var tmpUint32 uint32
	if tmpUint32, err = sync.ReadUint32(); err != nil {
		return DeviceFileInfo{}, fmt.Errorf("sync transport read (time): %w", err)
	}
	entry.LastModified = time.Unix(int64(tmpUint32), 0)
	logs.WriteString(entry.LastModified.String() + "\t")

	if tmpUint32, err = sync.ReadUint32(); err != nil {
		return DeviceFileInfo{}, fmt.Errorf("sync transport read (file name length): %w", err)
	}
	logs.WriteString(fmt.Sprintf("%d\t", tmpUint32))

	if entry.Name, err = sync.ReadStringN(int(tmpUint32)); err != nil {
		return DeviceFileInfo{}, fmt.Errorf("sync transport read (file name): %w", err)
	}
	logs.WriteString(entry.Name + "\t")

	return
}

func (sync syncTransport) ReadUint32() (n uint32, err error) {
	err = binary.Read(sync.sock, binary.LittleEndian, &n)
	return
}

func (sync syncTransport) ReadStringN(size int) (s string, err error) {
	var raw []byte
	if raw, err = sync.ReadBytesN(size); err != nil {
		return "", err
	}
	return string(raw), nil
}

func (sync syncTransport) ReadBytesN(size int) (raw []byte, err error) {
	_ = sync.sock.SetReadDeadline(time.Now().Add(time.Second * sync.readTimeout))
	return _readN(sync.sock, size)
}

func (sync syncTransport) Close() (err error) {
	if sync.sock == nil {
		return nil
	}
	_ = DisableTimeWait(sync.sock.(*net.TCPConn))
	return sync.sock.Close()
}
