package jt808

import (
	"encoding/binary"
	"errors"

	"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
)

var ErrResponseHeaderTooShort = errors.New("jt808 response header too short")

const (
	msgTerminalGeneralResponse = uint16(0x0001)
	msgTerminalRegister        = uint16(0x0100)
	msgPlatformGeneralResponse = uint16(0x8001)
	msgPlatformRegisterAck     = uint16(0x8100)
)

type AutoResponder struct {
	authCode string
}

func NewAutoResponder(authCode string) AutoResponder {
	if authCode == "" {
		authCode = "g7gps"
	}
	return AutoResponder{authCode: authCode}
}

func (r AutoResponder) Respond(raw []byte, env envelope.FrameEnvelope) ([]byte, bool, error) {
	if env.ParseStatus == envelope.ParseBadFrame {
		return nil, false, nil
	}
	header, err := parseResponseHeader(raw)
	if err != nil {
		return nil, false, err
	}
	if header.messageID == msgTerminalGeneralResponse {
		return nil, false, nil
	}
	if header.messageID == msgTerminalRegister {
		body := make([]byte, 3, 3+len(r.authCode))
		binary.BigEndian.PutUint16(body[0:2], header.sequence)
		body[2] = 0
		body = append(body, []byte(r.authCode)...)
		return encodeResponse(msgPlatformRegisterAck, header, body), true, nil
	}
	body := make([]byte, 5)
	binary.BigEndian.PutUint16(body[0:2], header.sequence)
	binary.BigEndian.PutUint16(body[2:4], header.messageID)
	body[4] = 0
	if header.messageID == 0x0102 && env.AuthenticationEnforced && env.AuthenticationStatus != "accepted" {
		body[4] = 1
	}
	return encodeResponse(msgPlatformGeneralResponse, header, body), true, nil
}

type responseHeader struct {
	messageID       uint16
	versioned       bool
	protocolVersion byte
	phoneBCD        []byte
	sequence        uint16
}

func parseResponseHeader(raw []byte) (responseHeader, error) {
	if len(raw) < 12 {
		return responseHeader{}, ErrResponseHeaderTooShort
	}
	props := binary.BigEndian.Uint16(raw[2:4])
	versioned := props&0x4000 != 0
	phoneStart := 4
	phoneLen := 6
	protocolVersion := byte(0)
	if versioned {
		if len(raw) < 17 {
			return responseHeader{}, ErrResponseHeaderTooShort
		}
		protocolVersion = raw[4]
		phoneStart = 5
		phoneLen = 10
	}
	sequenceStart := phoneStart + phoneLen
	if len(raw) < sequenceStart+2 {
		return responseHeader{}, ErrResponseHeaderTooShort
	}
	phone := append([]byte(nil), raw[phoneStart:sequenceStart]...)
	return responseHeader{
		messageID:       binary.BigEndian.Uint16(raw[0:2]),
		versioned:       versioned,
		protocolVersion: protocolVersion,
		phoneBCD:        phone,
		sequence:        binary.BigEndian.Uint16(raw[sequenceStart : sequenceStart+2]),
	}, nil
}

func encodeResponse(messageID uint16, header responseHeader, body []byte) []byte {
	props := uint16(len(body)) & 0x03ff
	if header.versioned {
		props |= 0x4000
	}
	payload := make([]byte, 0, 4+1+len(header.phoneBCD)+2+len(body)+1)
	payload = appendU16(payload, messageID)
	payload = appendU16(payload, props)
	if header.versioned {
		payload = append(payload, header.protocolVersion)
	}
	payload = append(payload, header.phoneBCD...)
	payload = appendU16(payload, header.sequence)
	payload = append(payload, body...)
	payload = append(payload, checksum(payload))
	return frame(payload)
}

func appendU16(out []byte, value uint16) []byte {
	return append(out, byte(value>>8), byte(value))
}

func frame(payload []byte) []byte {
	out := []byte{0x7e}
	for _, value := range payload {
		switch value {
		case 0x7e:
			out = append(out, 0x7d, 0x02)
		case 0x7d:
			out = append(out, 0x7d, 0x01)
		default:
			out = append(out, value)
		}
	}
	out = append(out, 0x7e)
	return out
}
