package main

import (
	"bytes"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/big"
	"net"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"
)

// Config
var (
	TargetHTTPHost = "https://api.asmodee.net"
	TargetTCPHost  = "got.games.asmodee.net"
	TargetTCPPort  = "2445"
	ProxyHTTPPort  = ":8080"
	ProxyTCPPort   = ":3000"
	LogFileName    = ""
	LogFile        *os.File
	LogMutex       sync.Mutex
)

// Log Entry Structure
type LogEntry struct {
	Timestamp string      `json:"timestamp"`
	Protocol  string      `json:"protocol"` // HTTP or TCP
	Type      string      `json:"type"`     // REQUEST or RESPONSE
	Summary   string      `json:"summary"`  // e.g. "GET /v1/foo" or "Packet 123"
	Data      interface{} `json:"data"`     // Structured data or raw info
}

// Write to JSONL
func logJSON(entry LogEntry) {
	entry.Timestamp = time.Now().Format(time.RFC3339Nano)
	LogMutex.Lock()
	defer LogMutex.Unlock()

	bytes, err := json.Marshal(entry)
	if err == nil {
		if LogFile != nil {
			LogFile.Write(bytes)
			LogFile.WriteString("\n")
		} else {
			fmt.Println(string(bytes))
		}
	}
}

func main() {
	flag.StringVar(&TargetHTTPHost, "target-http", "https://api.asmodee.net", "Target HTTP Host")
	flag.StringVar(&TargetTCPHost, "target-tcp-host", "got.games.asmodee.net", "Target TCP Host")
	flag.StringVar(&TargetTCPPort, "target-tcp-port", "2445", "Target TCP Port")
	flag.Parse()

	LogFileName = fmt.Sprintf("session_%s.jsonl", time.Now().Format("20060102_150405"))
	f, err := os.Create(LogFileName)
	if err != nil {
		log.Fatal(err)
	}
	LogFile = f
	defer LogFile.Close()

	fmt.Printf("Starting Proxy...\nLog: %s\nHTTP Target: %s\nTCP Target Default: %s:%s\n", LogFileName, TargetHTTPHost, TargetTCPHost, TargetTCPPort)

	// Start HTTP Proxy
	go startHTTPProxy()

	// Start TCP Proxy
	startTCPProxy()
}

// --- HTTP Proxy ---

func startHTTPProxy() {
	http.HandleFunc("/", handleHTTPRequest)
	fmt.Println("[HTTP] Listening on " + ProxyHTTPPort)
	log.Fatal(http.ListenAndServe(ProxyHTTPPort, nil))
}

func handleHTTPRequest(w http.ResponseWriter, r *http.Request) {
	// Log Request
	bodyBytes, _ := ioutil.ReadAll(r.Body)
	r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // Restore for forwarding

	logJSON(LogEntry{
		Protocol: "HTTP",
		Type:     "REQUEST",
		Summary:  fmt.Sprintf("%s %s", r.Method, r.URL.Path),
		Data: map[string]interface{}{
			"headers": r.Header,
			"body":    string(bodyBytes),
		},
	})

	// Forward Request
	targetURL := TargetHTTPHost + r.URL.Path
	if r.URL.RawQuery != "" {
		targetURL += "?" + r.URL.RawQuery
	}

	proxyReq, err := http.NewRequest(r.Method, targetURL, bytes.NewBuffer(bodyBytes))
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}
	// Copy headers
	for name, values := range r.Header {
		for _, value := range values {
			proxyReq.Header.Add(name, value)
		}
	}
	// Remove Hop-by-hop headers
	proxyReq.Header.Del("Host") // Let http client set it
	// Might need to set Host header manually if target expects it
	// proxyReq.Host = ...

	client := &http.Client{}
	resp, err := client.Do(proxyReq)
	if err != nil {
		// Log Error
		logJSON(LogEntry{
			Protocol: "HTTP",
			Type:     "ERROR",
			Summary:  "Forwarding Failed",
			Data:     err.Error(),
		})
		http.Error(w, err.Error(), http.StatusBadGateway)
		return
	}
	defer resp.Body.Close()

	respBody, _ := ioutil.ReadAll(resp.Body)

	// Log Response
	logJSON(LogEntry{
		Protocol: "HTTP",
		Type:     "RESPONSE",
		Summary:  fmt.Sprintf("%s %d", r.URL.Path, resp.StatusCode),
		Data: map[string]interface{}{
			"status":  resp.StatusCode,
			"headers": resp.Header,
			"body":    string(respBody),
		},
	})

	// Sniff for TCP Info
	// Look for "server": { "host": ... } or "HostName": ...
	sniffTCPInfo(respBody)

	// Write Response
	for name, values := range resp.Header {
		for _, value := range values {
			w.Header().Add(name, value)
		}
	}
	w.WriteHeader(resp.StatusCode)
	w.Write(respBody)
}

func sniffTCPInfo(body []byte) {
	// Search for "host":"..." and "port":... pattern
	s := string(body)
	if strings.Contains(s, "\"host\"") && strings.Contains(s, "\"port\"") {
		idxHost := strings.Index(s, "\"host\"")
		if idxHost != -1 {
			// Find value
			startQuote := strings.Index(s[idxHost:], ":") + idxHost + 1
			startQuote = strings.Index(s[startQuote:], "\"") + startQuote
			endQuote := strings.Index(s[startQuote+1:], "\"") + startQuote + 1

			if startQuote != -1 && endQuote != -1 {
				extractedHost := s[startQuote+1 : endQuote]
				if extractedHost != "" {
					fmt.Printf("[PROXY] Sniffed TCP Host: %s\n", extractedHost)
					TargetTCPHost = extractedHost
				}
			}
		}

		idxPort := strings.Index(s, "\"port\"")
		if idxPort != -1 {
			// Find value (number)
			colon := strings.Index(s[idxPort:], ":") + idxPort
			// Find first digit after colon
			startNum := -1
			endNum := -1
			for i := colon + 1; i < len(s); i++ {
				c := s[i]
				if c >= '0' && c <= '9' {
					if startNum == -1 {
						startNum = i
					}
				} else if startNum != -1 {
					// End of number
					endNum = i
					break
				}
			}
			if startNum != -1 {
				if endNum == -1 {
					endNum = len(s)
				}
				extractedPort := s[startNum:endNum]
				fmt.Printf("[PROXY] Sniffed TCP Port: %s\n", extractedPort)
				TargetTCPPort = extractedPort
			}
		}
	}
}

func findHostPortRecursive(obj interface{}) {
	switch v := obj.(type) {
	case map[string]interface{}:
		if h, ok := v["host"].(string); ok {
			TargetTCPHost = h
			fmt.Printf("[PROXY] Sniffed TCP Host: %s\n", h)
		}
		if p, ok := v["port"].(float64); ok { // JSON numbers are float64 in generic interface
			TargetTCPPort = fmt.Sprintf("%d", int(p))
			fmt.Printf("[PROXY] Sniffed TCP Port: %s\n", TargetTCPPort)
		}
		for _, val := range v {
			findHostPortRecursive(val)
		}
	case []interface{}:
		for _, val := range v {
			findHostPortRecursive(val)
		}
	}
}

// --- TCP Proxy ---

func startTCPProxy() {
	cert, err := generateSelfSignedCert()
	if err != nil {
		log.Fatal(err)
	}

	listener, err := tls.Listen("tcp", ProxyTCPPort, cert)
	if err != nil {
		log.Fatalf("TCP Listen failed: %v", err)
	}
	fmt.Println("[TCP] Listening on " + ProxyTCPPort)

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println("TCP Accept error:", err)
			continue
		}
		go handleTCPConn(conn)
	}
}

func handleTCPConn(clientConn net.Conn) {
	defer clientConn.Close()

	// Connect to Real Server
	targetAddr := fmt.Sprintf("%s:%s", TargetTCPHost, TargetTCPPort)
	fmt.Printf("[TCP] New Connection. Forwarding to %s\n", targetAddr)

	// Use TLS to connect to Real Server
	serverConn, err := tls.Dial("tcp", targetAddr, &tls.Config{
		InsecureSkipVerify: true, // We assume official server cert is fine but we skip verify to avoid setup
	})
	if err != nil {
		log.Printf("Failed to dial target %s: %v\n", targetAddr, err)
		return
	}
	defer serverConn.Close()

	// Pipe
	var wg sync.WaitGroup
	wg.Add(2)

	// Client -> Server
	go func() {
		defer wg.Done()
		interceptAndLogTCP(clientConn, serverConn, "CLIENT->SERVER")
	}()

	// Server -> Client
	go func() {
		defer wg.Done()
		interceptAndLogTCP(serverConn, clientConn, "SERVER->CLIENT")
	}()

	wg.Wait()
}

func interceptAndLogTCP(src, dst net.Conn, direction string) {
	// We need to parse frames to log them effectively?
	// The protocol is: 4 bytes length + Payload.
	// Payload: Varint PacketID, Varint RequestID, Payload.

	// We will try to read in a buffer loop, parse length prefix, log, then forward.
	// But simply copying stream `io.Copy` is safer for latency.
	// However, we want to LOG. So we MUST read packet by packet.

	header := make([]byte, 4)
	for {
		// Read Head
		_, err := io.ReadFull(src, header)
		if err != nil {
			break
		}
		length := binary.BigEndian.Uint32(header)

		// Read Body
		body := make([]byte, length)
		_, err = io.ReadFull(src, body)
		if err != nil {
			break
		}

		// Write to Dst
		// Write header + body
		dst.Write(header)
		dst.Write(body)

		// Log
		parseAndLogPacket(direction, body)
	}
}

func parseAndLogPacket(direction string, data []byte) {
	// Basic Decode of Packet Wrapper
	// Packet ID (Field 1, Varint)
	// Payload (Field 3, Bytes) -> Message

	r := bytes.NewReader(data)

	packetID := int64(-1)
	var payloadBytes []byte

	for {
		tag, err := binary.ReadUvarint(r)
		if err != nil {
			break
		}
		fieldNum := tag >> 3
		wireType := tag & 7

		if fieldNum == 1 && wireType == 0 {
			packetID, _ = binary.ReadVarint(r) // ReadVarint for int64
		} else if fieldNum == 3 && wireType == 2 {
			l, _ := binary.ReadUvarint(r)
			payloadBytes = make([]byte, l)
			r.Read(payloadBytes)
		} else {
			// Skip
			skip(r, wireType)
		}
	}

	// If we found payload, parse Message
	requestID := int64(-1)
	if payloadBytes != nil {
		pr := bytes.NewReader(payloadBytes)
		for {
			tag, err := binary.ReadUvarint(pr)
			if err != nil {
				break
			}
			fieldNum := tag >> 3
			wireType := tag & 7

			if fieldNum == 1 && wireType == 0 { // Message.request_number
				requestID, _ = binary.ReadVarint(pr)
			} else {
				skip(pr, wireType)
			}
		}
	}

	logJSON(LogEntry{
		Protocol: "TCP",
		Type:     direction,
		Summary:  fmt.Sprintf("Packet %d | Req %d | Len %d", packetID, requestID, len(data)),
		Data: map[string]interface{}{
			"packet_id":  packetID,
			"request_id": requestID,
			"hex":        hex.EncodeToString(data),
		},
	})
}

func skip(r *bytes.Reader, wireType uint64) {
	switch wireType {
	case 0:
		binary.ReadUvarint(r)
	case 1:
		r.Seek(8, io.SeekCurrent)
	case 2:
		l, _ := binary.ReadUvarint(r)
		r.Seek(int64(l), io.SeekCurrent)
	case 5:
		r.Seek(4, io.SeekCurrent)
	}
}

// --- Cert Gen (Copied) ---
func generateSelfSignedCert() (*tls.Config, error) {
	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return nil, err
	}
	template := x509.Certificate{
		SerialNumber:          big.NewInt(1),
		Subject:               pkix.Name{Organization: []string{"Proxy"}},
		NotBefore:             time.Now(),
		NotAfter:              time.Now().Add(time.Hour * 24),
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		BasicConstraintsValid: true,
		IPAddresses:           []net.IP{net.ParseIP("127.0.0.1")},
		DNSNames:              []string{"localhost"},
	}
	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
	if err != nil {
		return nil, err
	}
	return &tls.Config{Certificates: []tls.Certificate{{Certificate: [][]byte{derBytes}, PrivateKey: priv}}}, nil
}
