package mysql

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"git.loafle.net/overflow/overflow_probe/discovery/core/scan/service/matcher/packet"
	"git.loafle.net/overflow/overflow_probe/discovery/discovery/types"
	"strconv"
	"strings"
)

type PacketSize struct {
	PacketLength [3]byte
	PacketNumber byte
}

type mySql struct {
	Payload  PacketSize
	Protocol byte
	Version  [256]byte

	TreadId       uint32
	Salt1         [9]byte
	ServerCapa    uint16
	ServerLang    uint8
	ServerStat    uint16
	ExtServerCapa uint16
	AuthPlugLen   uint8
	_             [10]uint8
	Salt2         [13]uint8
	AuthPlugName  [64]uint8
}

type MySqlMatcher struct {
	packets   []*packet.Packet
	version   string
	isErrResp bool
	errCode   int
	errMsg    string
	isSSL     bool
}

func NewMySqlMatcher() *MySqlMatcher {
	return &MySqlMatcher{}
}

func (t *MySqlMatcher) ServiceName() string {
	if t.isErrResp {
		return "MySQL" + "(Err-" + strconv.Itoa(t.errCode) + " : " + t.errMsg + ")"
	}
	if t.isSSL {
		return "MySQL" + "-" + t.version + "(SSL)"
	}
	return "MySQL" + "(" + t.version + ")"
}

func (t *MySqlMatcher) PacketCount() int {
	return len(t.packets)
}

func (t *MySqlMatcher) Packet(index int) *packet.Packet {
	return t.packets[index]
}

func (t *MySqlMatcher) IsError(index int, packet *packet.Packet, info *types.ServiceScanInfo) bool {
	return false
}

func (t *MySqlMatcher) IsNoResponse(index int) bool {
	return false
}

func (t *MySqlMatcher) IsPrePacket() bool {
	return true
}

func (t *MySqlMatcher) Match(index int, packet *packet.Packet, info *types.ServiceScanInfo) bool {

	if packet == nil || len(packet.Buffer) <= 0 {
		return false
	}

	r := new(bytes.Buffer)
	r.Write(packet.Buffer)

	m := mySql{}
	if err := binary.Read(r, binary.LittleEndian, &m); err != nil {
		return false
	}

	buf := bytes.NewBuffer(m.Payload.PacketLength[:])
	packetLen, _ := binary.ReadUvarint(buf)

	if packetLen != uint64(packet.Len-4) {
		return false
	}

	if m.Protocol == 0xff {
		//MySQL error response
		var code [2]uint8
		copy(code[:], m.Version[:2])

		var msg [256]uint8
		copy(msg[:], m.Version[2:])

		errCode := binary.LittleEndian.Uint16(code[:])
		if errCode < 1000 || errCode > 1727 {
			return false
		}
		errMsg := bytes.Trim(msg[:], "\x00")

		t.isErrResp = true
		t.errCode = int(errCode)
		t.errMsg = string(errMsg)

		return true
	}

	if m.Protocol != 10 && m.Protocol != 9 {
		return false
	}

	t.checkSSL(packet)

	return true
}

func (t *MySqlMatcher) checkSSL(packet *packet.Packet) {

	temp := make([]byte, packet.Len)
	r := new(bytes.Buffer)
	r.Write(packet.Buffer)

	if err := binary.Read(r, binary.LittleEndian, &temp); err != nil {
		return
	}

	t.version = strings.Split(string(packet.Buffer)[5:packet.Len], "\x00")[0]
	versionLen := len(t.version) + 1
	data := binary.LittleEndian.Uint16(temp[18+versionLen : 20+versionLen])
	s := fmt.Sprintf("%b", data)

	for i, b := range s {
		if i == 4 {
			if b == 49 {
				t.isSSL = true
			}
		}
	}
}
