package signer

import (
	"errors"
	"fmt"
	"io"
	"time"

	log "github.com/sirupsen/logrus"

	"git.cacert.org/cacert-gosigner/datastructures"
	"git.cacert.org/cacert-gosigner/shared"
)

const (
	waitForHandShake    = time.Second * 5
	waitForInitialByte  = time.Second * 20
	waitForLengthBytes  = time.Second * 2
	waitForChecksumByte = time.Second * 2
	waitForTrailerBytes = time.Second * 2
	waitForBlock        = time.Minute * 1

	maxTriesPerBlock = 10_000
)

// The PortHandler takes care of reading and writing of raw bytes from/to a serial port
type PortHandler struct {
	port        io.ReadWriteCloser
	processor   *CommandProcessor
	errors      chan error
	commandChan chan *datastructures.SignerRequest
}

func (p *PortHandler) MainLoop() {
	count := 0
	for {
		go p.Receive()

		select {
		case command := <-p.commandChan:
			if command == nil {
				log.Infof("command channel has been closed. stopping execution.")
				return
			}
			log.Debugf("received command %v", command)
			response, err := p.processor.Process(*command)
			if err != nil {
				log.Errorf("error processing command: %v", err)
				close(p.commandChan)
			} else {
				_ = p.SendResponse(response)
			}
		case err := <-p.errors:
			log.Errorf("error from io goroutine %v", err)
		}

		count++
		log.Infof("%d requests processed. Waiting for next request ...", count)
	}
}

// Receive a request and generate a request data structure
func (p *PortHandler) Receive() {
	header, err := shared.ReceiveBytes(p.port, 1, waitForInitialByte)
	if err != nil {
		p.errors <- err
		return
	}
	if header[0] != shared.HandshakeByte {
		p.errors <- fmt.Errorf(
			"unexpected byte 0x%x expected 0x%x",
			header[0],
			shared.HandshakeByte,
		)
		return
	}

	tries := maxTriesPerBlock

	var command *datastructures.SignerRequest
	for {
		if tries <= 0 {
			p.errors <- errors.New("tried reading block too often")
			break
		}
		tries--
		if err := shared.SendBytes(p.port, []byte{shared.AckByte}); err != nil {
			p.errors <- fmt.Errorf("could not write ACK byte: %v", err)
			break
		}

		lengthBytes, err := shared.ReceiveBytes(p.port, shared.LengthFieldSize, waitForLengthBytes)
		if err != nil {
			p.errors <- fmt.Errorf("could not read lenght bytes: %v", err)
			break
		}
		blockLength := datastructures.Decode24BitLength(lengthBytes)
		blockData, err := shared.ReceiveBytes(p.port, blockLength, waitForBlock)
		if err != nil {
			p.errors <- fmt.Errorf("could not read data block: %v", err)
			if !p.requestResend() {
				break
			}
			continue
		}

		checkSum, err := shared.ReceiveBytes(p.port, shared.CheckSumFieldSize, waitForChecksumByte)
		if err != nil {
			p.errors <- fmt.Errorf("could not read checksum byte: %v", err)
			break
		}

		calculated := datastructures.CalculateXorCheckSum([][]byte{lengthBytes, blockData})
		if checkSum[0] != calculated {
			p.errors <- fmt.Errorf("CRC error. expected 0x%02x, got 0x%02x", calculated, checkSum[0])
			if !p.requestResend() {
				break
			}
			continue
		}

		trailer, err := shared.ReceiveBytes(p.port, shared.TrailerFieldSize, waitForTrailerBytes)
		if err != nil {
			p.errors <- fmt.Errorf("could not read trailer bytes: %v", err)
			break
		}

		if string(trailer) != shared.MagicTrailer {
			p.errors <- fmt.Errorf(
				"BROKEN block detected, expected trailer bytes '%s' not found, got '%s' instead",
				shared.MagicTrailer,
				trailer,
			)
			if !p.requestResend() {
				break
			}
			continue
		}

		command, err = datastructures.SignerRequestFromData(blockData)
		if err != nil {
			p.errors <- fmt.Errorf("failed to parse block as signer request: %v", err)
			break
		}

		if err := shared.SendBytes(p.port, []byte{shared.AckByte}); err != nil {
			p.errors <- fmt.Errorf("failed to send ACK byte: %v", err)
			break
		}

		p.commandChan <- command
		break
	}
}

func (p *PortHandler) requestResend() bool {
	if err := shared.SendBytes(p.port, []byte{shared.ResendByte}); err != nil {
		p.errors <- err
		return false
	}
	return true
}

// Send a response to the client
func (p *PortHandler) SendResponse(response *datastructures.SignerResponse) error {
	if err := shared.SendBytes(p.port, []byte{shared.HandshakeByte}); err != nil {
		return err
	}

	if ack, err := shared.ReceiveBytes(p.port, 1, waitForHandShake); err != nil {
		return err
	} else if ack[0] != shared.AckByte {
		return errors.New(fmt.Sprintf("invalid ack byte 0x%02x", ack[0]))
	}

	tryAgain := true
	for tryAgain {
		data := response.Serialize()
		if err := shared.SendBytes(p.port, data); err != nil {
			return err
		}

		checksum := datastructures.CalculateXorCheckSum([][]byte{data})
		if err := shared.SendBytes(p.port, []byte{checksum}); err != nil {
			return err
		}

		if err := shared.SendBytes(p.port, []byte(shared.MagicTrailer)); err != nil {
			return err
		}

		if ack, err := shared.ReceiveBytes(p.port, 1, waitForHandShake); err != nil {
			return err
		} else if ack[0] == shared.AckByte {
			tryAgain = false
		} else if ack[0] != 0x11 {
			return errors.New(fmt.Sprintf("invalid ack byte 0x%02x", ack[0]))
		}
	}

	return nil
}

func NewSignerProcess(port io.ReadWriteCloser) *PortHandler {
	errorChan := make(chan error)
	return &PortHandler{
		port:        port,
		errors:      errorChan,
		commandChan: make(chan *datastructures.SignerRequest, 1),
		processor:   &CommandProcessor{},
	}
}