package bridge

import (
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"net"
	"sync"
	"sync/atomic"
	"time"

	"github.com/xtaci/smux"
	"github.com/wormhole/wormhole/internal/cache"
	"github.com/wormhole/wormhole/internal/protocol"
	"github.com/wormhole/wormhole/internal/store"
)

type AgentSession struct {
	ClientID    uint
	Version     string
	conn        net.Conn
	smux        *smux.Session
	mu          sync.Mutex
	closed      atomic.Bool
	activeConns atomic.Int32
}

type Bridge struct {
	cache    *cache.Manager
	store    *store.Store
	mu       sync.RWMutex
	agents   map[uint]*AgentSession
	listener net.Listener
}

func New(c *cache.Manager, st *store.Store) *Bridge {
	return &Bridge{
		cache:  c,
		store:  st,
		agents: make(map[uint]*AgentSession),
	}
}

func (b *Bridge) Start(addr string, certFile, keyFile string) error {
	var ln net.Listener
	var err error
	if certFile != "" && keyFile != "" {
		cert, err := tls.LoadX509KeyPair(certFile, keyFile)
		if err != nil {
			return fmt.Errorf("load bridge tls cert: %w", err)
		}
		ln, err = tls.Listen("tcp", addr, &tls.Config{
			Certificates: []tls.Certificate{cert},
			MinVersion:   tls.VersionTLS12,
		})
		if err != nil {
			return err
		}
		log.Printf("[bridge] listening on %s (TLS)", addr)
	} else {
		ln, err = net.Listen("tcp", addr)
		if err != nil {
			return err
		}
		log.Printf("[bridge] listening on %s (plain TCP, configure TLS cert for production)", addr)
	}
	b.listener = ln
	go b.acceptLoop()
	b.startWatchdog()
	return nil
}

func (b *Bridge) Shutdown() error {
	if b.listener != nil {
		return b.listener.Close()
	}
	return nil
}

func (b *Bridge) acceptLoop() {
	for {
		conn, err := b.listener.Accept()
		if err != nil {
			return
		}
		go b.handleConn(conn)
	}
}

func (b *Bridge) validateClient(client *store.Client) error {
	if !client.Status {
		return fmt.Errorf("client disabled")
	}
	if client.ExpireAt != nil && time.Now().After(*client.ExpireAt) {
		return fmt.Errorf("client expired")
	}
	return nil
}

func (b *Bridge) handleConn(conn net.Conn) {
	defer conn.Close()
	SetTCPKeepAlive(conn)
	msgType, body, err := protocol.ReadMessage(conn)
	if err != nil {
		log.Printf("[bridge] read register failed: %v", err)
		return
	}
	if msgType != protocol.MsgRegister {
		return
	}
	reg, err := protocol.ParseRegister(body)
	if err != nil {
		return
	}
	client, ok := b.cache.GetClientByKey(reg.VerifyKey)
	if !ok {
		_ = protocol.WriteMessage(conn, protocol.MsgRegisterAck, protocol.RegisterAckPayload{
			OK:      false,
			Message: "invalid verify key",
		})
		return
	}
	if err := b.validateClient(client); err != nil {
		_ = protocol.WriteMessage(conn, protocol.MsgRegisterAck, protocol.RegisterAckPayload{
			OK:      false,
			Message: err.Error(),
		})
		return
	}
	if err := protocol.WriteMessage(conn, protocol.MsgRegisterAck, protocol.RegisterAckPayload{
		OK:       true,
		ClientID: client.ID,
		Message:  "registered",
	}); err != nil {
		return
	}

	session, err := smux.Client(conn, smuxConfig())
	if err != nil {
		log.Printf("[bridge] smux client failed: %v", err)
		return
	}

	agent := &AgentSession{
		ClientID: client.ID,
		Version:  reg.Version,
		conn:     conn,
		smux:     session,
	}
	b.mu.Lock()
	if old, exists := b.agents[client.ID]; exists {
		old.Close()
	}
	b.agents[client.ID] = agent
	b.mu.Unlock()

	b.cache.SetClientOnline(client.ID, reg.Version)
	log.Printf("[bridge] client %d online (version=%s)", client.ID, reg.Version)

	b.controlLoop(agent)

	b.mu.Lock()
	delete(b.agents, client.ID)
	b.mu.Unlock()
	b.cache.SetClientOffline(client.ID)
	log.Printf("[bridge] client %d offline", client.ID)
}

func (b *Bridge) controlLoop(agent *AgentSession) {
	for {
		stream, err := agent.smux.AcceptStream()
		if err != nil {
			agent.Close()
			return
		}
		go b.handleControlStream(agent, stream)
	}
}

func (b *Bridge) handleControlStream(agent *AgentSession, stream *smux.Stream) {
	defer stream.Close()
	msgType, body, err := protocol.ReadMessage(stream)
	if err != nil {
		return
	}
	switch msgType {
	case protocol.MsgPing:
		b.cache.TouchAgent(agent.ClientID)
		_ = protocol.WriteMessage(stream, protocol.MsgPong, nil)
	case protocol.MsgClose:
		agent.Close()
	}
	_ = body
}

func (a *AgentSession) Close() {
	if a.closed.Swap(true) {
		return
	}
	if a.smux != nil {
		_ = a.smux.Close()
	}
	if a.conn != nil {
		_ = a.conn.Close()
	}
}

func (b *Bridge) OpenStream(clientID uint, meta *protocol.LinkMeta) (net.Conn, error) {
	client, err := b.store.GetClientByID(clientID)
	if err != nil {
		return nil, fmt.Errorf("client %d not found", clientID)
	}
	if err := b.validateClient(client); err != nil {
		return nil, err
	}

	b.mu.RLock()
	agent, ok := b.agents[clientID]
	b.mu.RUnlock()
	if !ok {
		return nil, fmt.Errorf("client %d offline", clientID)
	}
	if agent.closed.Load() {
		return nil, fmt.Errorf("client %d session closed", clientID)
	}
	if client.MaxConn > 0 && int(agent.activeConns.Load()) >= client.MaxConn {
		return nil, fmt.Errorf("client %d max connections reached", clientID)
	}

	stream, err := agent.smux.OpenStream()
	if err != nil {
		return nil, err
	}
	if err := protocol.WriteMessage(stream, protocol.MsgOpenStream, meta); err != nil {
		stream.Close()
		return nil, err
	}
	msgType, _, err := protocol.ReadMessage(stream)
	if err != nil {
		stream.Close()
		return nil, err
	}
	if msgType != protocol.MsgStreamReady {
		stream.Close()
		return nil, fmt.Errorf("stream not ready")
	}

	agent.activeConns.Add(1)
	var wrapped net.Conn = &trackedConn{
		Conn:   stream,
		onClose: func() { agent.activeConns.Add(-1) },
	}
	if client.RateLimit > 0 {
		wrapped = newRateLimitedConn(wrapped, client.RateLimit*1024)
	}
	return wrapped, nil
}

func (b *Bridge) IsOnline(clientID uint) bool {
	b.mu.RLock()
	defer b.mu.RUnlock()
	_, ok := b.agents[clientID]
	return ok
}

func (b *Bridge) OnlineCount() int {
	b.mu.RLock()
	defer b.mu.RUnlock()
	return len(b.agents)
}

type trackedConn struct {
	net.Conn
	onClose func()
	once    sync.Once
}

func (c *trackedConn) Close() error {
	c.once.Do(c.onClose)
	return c.Conn.Close()
}

func CopyBoth(a, b net.Conn) (inlet, export int64) {
	var inVal, outVal int64
	done := make(chan struct{}, 2)
	go func() {
		n, _ := io.Copy(b, a)
		atomic.AddInt64(&outVal, n)
		done <- struct{}{}
	}()
	go func() {
		n, _ := io.Copy(a, b)
		atomic.AddInt64(&inVal, n)
		done <- struct{}{}
	}()
	<-done
	<-done
	return atomic.LoadInt64(&inVal), atomic.LoadInt64(&outVal)
}

func CopyBothIO(a io.ReadWriteCloser, b io.ReadWriteCloser) (inlet, export int64) {
	var inVal, outVal int64
	done := make(chan struct{}, 2)
	go func() {
		n, _ := io.Copy(b, a)
		atomic.AddInt64(&outVal, n)
		done <- struct{}{}
	}()
	go func() {
		n, _ := io.Copy(a, b)
		atomic.AddInt64(&inVal, n)
		done <- struct{}{}
	}()
	<-done
	<-done
	return atomic.LoadInt64(&inVal), atomic.LoadInt64(&outVal)
}
