package peer

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"net/netip"
	"net/url"
	"sync"
	"sync/atomic"
	"vppn/m"
)

type peerMain struct {
	conf       localConfig
	rt         *atomic.Pointer[routingTable]
	ifReader   *ifReader
	connReader *connReader
	iface      io.Writer
	hubPoller  *hubPoller
	super      *supervisor
}

type mainArgs struct {
	NetName    string
	HubAddress string
	APIKey     string
}

func newPeerMain(args mainArgs) *peerMain {
	logf := func(s string, args ...any) {
		log.Printf("[Main] "+s, args...)
	}

	config, err := loadPeerConfig(args.NetName)
	if err != nil {
		logf("Failed to load configuration: %v", err)
		logf("Initializing...")
		initPeerWithHub(args)

		config, err = loadPeerConfig(args.NetName)
		if err != nil {
			log.Fatalf("Failed to load configuration: %v", err)
		}
	}

	state, err := loadNetworkState(args.NetName)
	if err != nil {
		log.Fatalf("Failed to load network state: %v", err)
	}

	iface, err := openInterface(config.Network, config.PeerIP, args.NetName)
	if err != nil {
		log.Fatalf("Failed to open interface: %v", err)
	}

	localPeer := state.Peers[config.PeerIP]

	myAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf(":%d", localPeer.Port))
	if err != nil {
		log.Fatalf("Failed to resolve UDP address: %v", err)
	}

	logf("Listening on %v...", myAddr)
	conn, err := net.ListenUDP("udp", myAddr)
	if err != nil {
		log.Fatalf("Failed to open UDP port: %v", err)
	}

	conn.SetReadBuffer(1024 * 1024 * 8)
	conn.SetWriteBuffer(1024 * 1024 * 8)

	// Wrap write function - this is necessary to avoid starvation.
	writeLock := sync.Mutex{}
	writeToUDPAddrPort := func(b []byte, addr netip.AddrPort) (n int, err error) {
		writeLock.Lock()
		n, err = conn.WriteToUDPAddrPort(b, addr)
		if err != nil {
			logf("Failed to write packet: %v", err)
		}
		writeLock.Unlock()
		return n, err
	}

	var localAddr netip.AddrPort
	ip, localAddrValid := netip.AddrFromSlice(localPeer.PublicIP)
	if localAddrValid {
		localAddr = netip.AddrPortFrom(ip, localPeer.Port)
	}

	rt := newRoutingTable(localPeer.PeerIP, localAddr)
	rtPtr := &atomic.Pointer[routingTable]{}
	rtPtr.Store(&rt)

	ifReader := newIFReader(iface, writeToUDPAddrPort, rtPtr)
	super := newSupervisor(writeToUDPAddrPort, rtPtr, config.PrivKey)
	connReader := newConnReader(conn.ReadFromUDPAddrPort, writeToUDPAddrPort, iface, super.HandleControlMsg, rtPtr)
	hubPoller, err := newHubPoller(config.PeerIP, args.NetName, args.HubAddress, args.APIKey, super.HandleControlMsg)
	if err != nil {
		log.Fatalf("Failed to create hub poller: %v", err)
	}

	return &peerMain{
		conf:       config,
		rt:         rtPtr,
		iface:      iface,
		ifReader:   ifReader,
		connReader: connReader,
		hubPoller:  hubPoller,
		super:      super,
	}
}

func (p *peerMain) Run() {

	go p.ifReader.Run()
	go p.connReader.Run()
	p.super.Start()

	if !p.rt.Load().LocalAddr.IsValid() {
		go runMCWriter(p.conf.PeerIP, p.conf.PrivSignKey)
		go runMCReader(p.rt, p.super.HandleControlMsg)
	}

	go p.hubPoller.Run()
	select {}
}

func initPeerWithHub(args mainArgs) {
	keys := generateKeys()

	initURL, err := url.Parse(args.HubAddress)
	if err != nil {
		log.Fatalf("Failed to parse hub URL: %v", err)
	}
	initURL.Path = "/peer/init/"

	initArgs := m.PeerInitArgs{
		EncPubKey:  keys.PubKey,
		PubSignKey: keys.PubSignKey,
	}

	buf := &bytes.Buffer{}
	if err := json.NewEncoder(buf).Encode(initArgs); err != nil {
		log.Fatalf("Failed to encode init args: %v", err)
	}

	req, err := http.NewRequest(http.MethodPost, initURL.String(), buf)
	if err != nil {
		log.Fatalf("Failed to construct request: %v", err)
	}
	req.SetBasicAuth("", args.APIKey)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatalf("Failed to init with hub: %v", err)
	}
	defer resp.Body.Close()

	data, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Fatalf("Failed to read response body: %v", err)
	}

	initResp := m.PeerInitResp{}
	if err := json.Unmarshal(data, &initResp); err != nil {
		log.Fatalf("Failed to parse configuration: %v\n%s", err, data)
	}

	config := localConfig{}
	config.PeerIP = initResp.PeerIP
	config.Network = initResp.Network
	config.PubKey = keys.PubKey
	config.PrivKey = keys.PrivKey
	config.PubSignKey = keys.PubSignKey
	config.PrivSignKey = keys.PrivSignKey

	if err := storeNetworkState(args.NetName, initResp.NetworkState); err != nil {
		log.Fatalf("Failed to store network state: %v", err)
	}

	if err := storePeerConfig(args.NetName, config); err != nil {
		log.Fatalf("Failed to store configuration: %v", err)
	}

	log.Print("Initialization successful.")
}
