package stream

import (
	"database/sql"
	"log"
	"sync"
	"time"

	"git.sharkk.net/EQ2/Protocol/opcodes"
	"git.sharkk.net/EQ2/Protocol/packets"
	"github.com/panjf2000/gnet/v2"
)

// StreamFactory manages EQStream instances using gnet
type StreamFactory struct {
	gnet.BuiltinEventEngine

	mu      sync.RWMutex
	streams map[gnet.Conn]*Stream

	// Configuration
	crcKey        uint32
	maxPacketSize uint16
	encodeKey     int
	decodeKey     int
	opcodeSize    uint8
	clientVersion int16

	// Database for opcodes
	db            *sql.DB
	opcodeTable   string
	opcodeManager opcodes.Manager

	// Callbacks
	onNewStream    func(*Stream)
	onStreamClosed func(*Stream)
	onPacket       func(*Stream, *packets.AppPacket)

	// Stats
	totalStreams  uint64
	activeStreams uint64
}

// FactoryConfig holds factory configuration
type FactoryConfig struct {
	CRCKey        uint32
	MaxPacketSize uint16
	EncodeKey     int
	DecodeKey     int
	OpcodeSize    uint8
	ClientVersion int16
	Database      *sql.DB
	OpcodeTable   string
}

// NewStreamFactory creates a new factory
func NewStreamFactory(cfg *FactoryConfig) *StreamFactory {
	f := &StreamFactory{
		streams:       make(map[gnet.Conn]*Stream),
		crcKey:        cfg.CRCKey,
		maxPacketSize: cfg.MaxPacketSize,
		encodeKey:     cfg.EncodeKey,
		decodeKey:     cfg.DecodeKey,
		opcodeSize:    cfg.OpcodeSize,
		clientVersion: cfg.ClientVersion,
		db:            cfg.Database,
		opcodeTable:   cfg.OpcodeTable,
	}

	if f.maxPacketSize == 0 {
		f.maxPacketSize = packets.DefaultMTU
	}
	if f.opcodeSize == 0 {
		f.opcodeSize = packets.DefaultOpcodeSize
	}
	if f.opcodeTable == "" {
		f.opcodeTable = "opcodes"
	}

	// Initialize opcode manager
	if cfg.ClientVersion > 0 && cfg.Database != nil {
		f.opcodeManager = opcodes.GetManager(cfg.ClientVersion, cfg.Database, f.opcodeTable)
	}

	return f
}

// OnBoot called when server starts
func (f *StreamFactory) OnBoot(eng gnet.Engine) gnet.Action {
	log.Printf("StreamFactory started")
	return gnet.None
}

// OnOpen called when new connection opens
func (f *StreamFactory) OnOpen(c gnet.Conn) ([]byte, gnet.Action) {
	// Create stream configuration
	streamCfg := &Config{
		SessionID:       uint32(time.Now().UnixNano()),
		CRCKey:          f.crcKey,
		MaxPacketSize:   f.maxPacketSize,
		EncodeKey:       f.encodeKey,
		DecodeKey:       f.decodeKey,
		OpcodeManager:   f.opcodeManager,
		OpcodeSize:      f.opcodeSize,
		AckThreshold:    200 * time.Millisecond,
		KeepaliveTime:   10 * time.Second,
		TimeoutDuration: 30 * time.Second,
	}

	// Create new stream
	stream := NewStream(c, streamCfg)

	// Set packet callback
	stream.SetPacketCallback(func(app *packets.AppPacket) {
		if f.onPacket != nil {
			f.onPacket(stream, app)
		}
	})

	// Store stream
	f.mu.Lock()
	f.streams[c] = stream
	f.activeStreams++
	f.totalStreams++
	f.mu.Unlock()

	// Notify callback
	if f.onNewStream != nil {
		f.onNewStream(stream)
	}

	c.SetContext(stream)
	log.Printf("New stream from %s", c.RemoteAddr())

	return nil, gnet.None
}

// OnClose called when connection closes
func (f *StreamFactory) OnClose(c gnet.Conn, err error) gnet.Action {
	f.mu.Lock()
	stream, exists := f.streams[c]
	if exists {
		delete(f.streams, c)
		f.activeStreams--
	}
	f.mu.Unlock()

	if stream != nil {
		stream.Close()

		// Notify callback
		if f.onStreamClosed != nil {
			f.onStreamClosed(stream)
		}

		log.Printf("Stream closed: %s", c.RemoteAddr())
	}

	return gnet.None
}

// OnTraffic handles incoming data
func (f *StreamFactory) OnTraffic(c gnet.Conn) gnet.Action {
	stream := c.Context().(*Stream)
	if stream == nil {
		return gnet.Close
	}

	// Read all available data
	buf, err := c.Next(-1)
	if err != nil {
		return gnet.Close
	}

	// Process packet
	if err := stream.Process(buf); err != nil {
		log.Printf("Stream process error: %v", err)
		return gnet.Close
	}

	return gnet.None
}

// OnTick called periodically to process stream queues
func (f *StreamFactory) OnTick() (time.Duration, gnet.Action) {
	f.mu.RLock()
	streams := make([]*Stream, 0, len(f.streams))
	for _, stream := range f.streams {
		streams = append(streams, stream)
	}
	f.mu.RUnlock()

	// Process queues for all streams
	for _, stream := range streams {
		stream.processQueues()
	}

	return 100 * time.Millisecond, gnet.None
}

// GetStream returns stream for a connection
func (f *StreamFactory) GetStream(c gnet.Conn) *Stream {
	f.mu.RLock()
	defer f.mu.RUnlock()
	return f.streams[c]
}

// GetAllStreams returns all active streams
func (f *StreamFactory) GetAllStreams() []*Stream {
	f.mu.RLock()
	defer f.mu.RUnlock()

	result := make([]*Stream, 0, len(f.streams))
	for _, stream := range f.streams {
		result = append(result, stream)
	}
	return result
}

// SetNewStreamCallback sets callback for new streams
func (f *StreamFactory) SetNewStreamCallback(fn func(*Stream)) {
	f.onNewStream = fn
}

// SetStreamClosedCallback sets callback for closed streams
func (f *StreamFactory) SetStreamClosedCallback(fn func(*Stream)) {
	f.onStreamClosed = fn
}

// SetPacketCallback sets global packet callback
func (f *StreamFactory) SetPacketCallback(fn func(*Stream, *packets.AppPacket)) {
	f.onPacket = fn
}

// UpdateOpcodeManager updates opcodes for a specific version
func (f *StreamFactory) UpdateOpcodeManager(version int16) {
	if f.db != nil {
		f.opcodeManager = opcodes.GetManager(version, f.db, f.opcodeTable)
		log.Printf("Updated opcode manager to version %d", version)
	}
}

// Stats returns factory statistics
func (f *StreamFactory) Stats() map[string]uint64 {
	f.mu.RLock()
	defer f.mu.RUnlock()

	return map[string]uint64{
		"total_streams":  f.totalStreams,
		"active_streams": f.activeStreams,
	}
}

// Run starts the factory as a UDP server
func Run(addr string, factory *StreamFactory) error {
	return gnet.Run(factory, addr,
		gnet.WithMulticore(true),
		gnet.WithReuseAddr(true),
		gnet.WithReusePort(true),
		gnet.WithTicker(true),
		gnet.WithTCPKeepAlive(0), // Disable for UDP
		gnet.WithSocketRecvBuffer(1024*1024),
		gnet.WithSocketSendBuffer(1024*1024),
	)
}

// RunTLS starts the factory with TLS (future enhancement)
func RunTLS(addr string, factory *StreamFactory, certFile, keyFile string) error {
	// TLS configuration would go here if needed
	return Run(addr, factory)
}
