package fwd

import (
	"bufio"
	"encoding/binary"
	"fmt"
	"io"
	"log/slog"
	"proxy-server/gateway/core"
	"proxy-server/utils"
	"strings"

	"errors"
)

func analysisAndLog(conn *core.Conn, reader io.Reader) error {
	buf := bufio.NewReader(reader)

	domain, proto, err := sniffing(buf)
	if err != nil {
		err = fmt.Errorf("sniffing error: %w", err)
	} else {
		slog.Debug(
			"用户访问记录",
			slog.Int("uid", int(conn.Auth.Payload.ID)),
			slog.String("user", conn.RemoteAddr().String()),
			slog.String("proxy", conn.Protocol),
			slog.String("node", conn.LocalAddr().String()),
			slog.String("proto", proto),
			slog.String("dest", conn.Dest.String()),
			slog.String("domain", domain),
		)
	}
	go func() {
		discord(buf)
	}()
	return err
}

func sniffing(reader *bufio.Reader) (string, string, error) {
	peek, err := reader.Peek(8)
	if err != nil {
		return "", "", fmt.Errorf("sniffing peek error: %w", err)
	}

	method, ok := isHttp(peek)
	if ok {
		domain, err := analysisHttp(reader)
		return domain, "http(" + method + ")", err
	}

	tlsType, tlsVersion, ok := isTls(peek)
	if ok {
		var domain string
		if tlsType == "handshake" {
			domain, err = analysisTls(reader)
		}
		return domain, "tls(" + tlsType + "," + tlsVersion + ")", err
	}

	return "nil", "tcp", nil
}

func isHttp(bytes []byte) (string, bool) {

	var blankIndex int
	for i := range bytes {
		if bytes[i] == ' ' {
			blankIndex = i
			break
		}
	}
	method := string(bytes[:blankIndex])

	switch method {
	case "GET",
		"POST",
		"PUT",
		"PATCH",
		"DELETE",
		"HEAD",
		"OPTIONS",
		"TRACE",
		"CONNECT":
		return method, true
	}

	return "", false
}

func isTls(bytes []byte) (string, string, bool) {

	var tlsType string
	switch bytes[0] {
	case 0x14:
		tlsType = "change-cipher-spec"
	case 0x15:
		tlsType = "alert"
	case 0x16:
		tlsType = "handshake"
	case 0x17:
		tlsType = "application-data"
	}

	var tlsVersion string
	if bytes[1] == 0x03 {
		switch bytes[2] {
		case 0x00:
			tlsVersion = "SSL3.0"
		case 0x01:
			tlsVersion = "TLS1.0"
		case 0x02:
			tlsVersion = "TLS1.1"
		case 0x03:
			tlsVersion = "TLS1.2"
		}
	}
	if tlsType != "" && tlsVersion != "" {
		return tlsType, tlsVersion, true
	} else {
		return "", "", false
	}
}

func analysisHttp(reader *bufio.Reader) (string, error) {

	// reade top
	top, err := httpReadLine(reader)
	if err != nil {
		return "", fmt.Errorf("http read top error: %w", err)
	}

	// read header
	host := strings.Split(top, " ")[1]
	for {
		line, err := httpReadLine(reader)
		if err != nil {
			return "", err
		}
		if line == "" {
			break
		}
		if strings.HasPrefix(line, "Host: ") {
			host = strings.TrimPrefix(line, "Host: ")
		}
	}

	return host, nil
}

func httpReadLine(reader *bufio.Reader) (line string, err error) {

	var lineStr strings.Builder
	for {
		line, prefix, err := reader.ReadLine()
		if err != nil {
			return "", fmt.Errorf("http read line error: %w", err)
		}
		lineStr.Write(line)
		if !prefix {
			break
		}
	}
	return lineStr.String(), nil
}

func analysisTls(reader *bufio.Reader) (string, error) {

	// tls record
	_, err := utils.ReadBuffer(reader, 5)
	if err != nil {
		return "", fmt.Errorf("https read head error: %w", err)
	}

	// tls type
	hsType, err := reader.ReadByte()
	if err != nil {
		return "", fmt.Errorf("https read hsType error: %w", err)
	}

	switch hsType {
	case 0x01: // client hello

		// length
		_, err = utils.ReadBuffer(reader, 3)
		if err != nil {
			return "", fmt.Errorf("https read tls length error: %w", err)
		}

		// version
		_, err = utils.ReadBuffer(reader, 2)
		if err != nil {
			return "", fmt.Errorf("https read version error: %w", err)
		}

		// random
		_, err = utils.ReadBuffer(reader, 32)
		if err != nil {
			return "", fmt.Errorf("https read random error: %w", err)
		}

		// session id length
		sessionIdLen, err := reader.ReadByte()
		if err != nil {
			return "", fmt.Errorf("https read sessionIdLen error: %w", err)
		}
		// session id
		_, err = utils.ReadBuffer(reader, int(sessionIdLen))
		if err != nil {
			return "", fmt.Errorf("https read sessionId error: %w", err)
		}

		// cipher suites length
		cLenBuf, err := utils.ReadBuffer(reader, 2)
		if err != nil {
			return "", fmt.Errorf("https read cLen error: %w", err)
		}
		cLen := binary.BigEndian.Uint16(cLenBuf)
		// cipher suites
		_, err = utils.ReadBuffer(reader, int(cLen))
		if err != nil {
			return "", fmt.Errorf("https read c error: %w", err)
		}

		// compression methods length
		cmLen, err := reader.ReadByte()
		if err != nil {
			return "", fmt.Errorf("https read cmLen error: %w", err)
		}
		// compression methods
		_, err = utils.ReadBuffer(reader, int(cmLen))
		if err != nil {
			return "", fmt.Errorf("https read cm error: %w", err)
		}

		// extensions length
		eLenBuf, err := utils.ReadBuffer(reader, 2)
		if err != nil {
			return "", fmt.Errorf("https read eLen error: %w", err)
		}
		eLen := binary.BigEndian.Uint16(eLenBuf)

		// extensions
		host := ""

		for i := 0; i < int(eLen); {

			// extension type
			eTypeBuf, err := utils.ReadBuffer(reader, 2)
			if err != nil {
				return "", fmt.Errorf("https read extension type error: %w", err)
			}
			eType := binary.BigEndian.Uint16(eTypeBuf)

			// extension length
			eLenBuf, err := utils.ReadBuffer(reader, 2)
			if err != nil {
				return "", fmt.Errorf("https read extension length error: %w", err)
			}
			eLen := binary.BigEndian.Uint16(eLenBuf)

			// server name
			if eType == 0x00 {
				// server name list length
				_, err = utils.ReadBuffer(reader, 2)
				if err != nil {
					return "", fmt.Errorf("https read server name list length error: %w", err)
				}
				// server name type
				_, err = reader.ReadByte()
				if err != nil {
					return "", fmt.Errorf("https read server name type error: %w", err)
				}
				// server name length
				sLenBuf, err := utils.ReadBuffer(reader, 2)
				if err != nil {
					return "", fmt.Errorf("https read server name length error: %w", err)
				}
				sLen := binary.BigEndian.Uint16(sLenBuf)
				// server name
				bytes, err := utils.ReadBuffer(reader, int(sLen))
				if err != nil {
					return "", fmt.Errorf("https read server name error: %w", err)
				}

				host = string(bytes)
				return host, nil

			} else {
				// other extension
				_, err = utils.ReadBuffer(reader, int(eLen))
				if err != nil {
					return "", fmt.Errorf("https read extension error: %w", err)
				}
			}
			i += 4 + int(eLen)
		}
	default:
		return "", nil
	}

	return "", errors.New("analysis https error")
}

func discord(reader *bufio.Reader) {
	_, err := io.Copy(io.Discard, reader)
	if err != nil {
		slog.Error("analysis discord err", "err", err)
	}
}
