package main

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/binary"
	"encoding/csv"
	"encoding/pem"
	"flag"
	"fmt"
	"io"
	"log"
	"math/big"
	"os"
	"strconv"
	"sync"
	"time"

	quic "github.com/AeonDave/mp-quic-go"
)

func generateTLSConfig() *tls.Config {
	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		panic(err)
	}
	template := x509.Certificate{SerialNumber: big.NewInt(1)}
	certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
	if err != nil {
		panic(err)
	}
	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})

	tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
	if err != nil {
		panic(err)
	}
	return &tls.Config{
		Certificates: []tls.Certificate{tlsCert},
		NextProtos:   []string{"mpquic-exp"},
	}
}

func main() {
	addr := flag.String("addr", "0.0.0.0:4242", "Address to listen on")
	maxPaths := flag.Int("maxpaths", 4, "Maximum number of paths")
	outCSV := flag.String("output", "app_metrics.csv", "Output CSV for application-level metrics")
	flag.Parse()

	listener, err := quic.ListenAddr(*addr, generateTLSConfig(), &quic.Config{
		MaxPaths: *maxPaths,
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Server listening on %s with max %d paths\n", *addr, *maxPaths)
	fmt.Printf("Logging app-level metrics to %s\n", *outCSV)

	// Open CSV
	f, err := os.OpenFile(*outCSV, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
	if err != nil {
		log.Fatal("Failed to open CSV:", err)
	}
	defer f.Close()

	writer := csv.NewWriter(f)
	writer.Write([]string{"sequence_number", "drone_send_time", "ground_recv_time", "latency_ns", "bytes_received"})
	writer.Flush()

	// Mutex to protect CSV writer if multiple streams are used
	var mu sync.Mutex

	for {
		conn, err := listener.Accept(context.Background())
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println("Client connected:", conn.RemoteAddr())

		go func(conn *quic.Conn) {
			for {
				stream, err := conn.AcceptStream(context.Background())
				if err != nil {
					fmt.Println("Session error:", err)
					return
				}
				go func(stream *quic.Stream) {
					header := make([]byte, 4)
					for {
						// Read 4-byte length prefix
						_, err := io.ReadFull(stream, header)
						if err != nil {
							fmt.Printf("Stream closed\n")
							return
						}
						
						length := binary.BigEndian.Uint32(header)
						if length < 20 {
							fmt.Printf("Warning: Invalid frame length %d\n", length)
							continue
						}
						
						// Read rest of the payload
						payload := make([]byte, length-4)
						_, err = io.ReadFull(stream, payload)
						if err != nil {
							fmt.Printf("Stream read error: %v\n", err)
							return
						}
						
						recvTime := time.Now().UnixNano()
						
						seqNum := binary.BigEndian.Uint64(payload[0:8])
						sendTime := binary.BigEndian.Uint64(payload[8:16])
						latency := recvTime - int64(sendTime)
						
						mu.Lock()
						writer.Write([]string{
							strconv.FormatUint(seqNum, 10),
							strconv.FormatUint(sendTime, 10),
							strconv.FormatInt(recvTime, 10),
							strconv.FormatInt(latency, 10),
							strconv.FormatUint(uint64(length), 10),
						})
						// Periodically flush? 
						if seqNum % 1000 == 0 {
							writer.Flush()
						}
						mu.Unlock()
					}
				}(stream)
			}
		}(conn)
	}
}
