package replica

import (
	"encoding/json"
	"fmt"
	"log/slog"
	"net"
	"os"
	"sync"
	"time"
)

// LogLevel represents the logging level
type LogLevel slog.Level

const (
	LevelDebug LogLevel = LogLevel(slog.LevelDebug)
	LevelInfo  LogLevel = LogLevel(slog.LevelInfo)
	LevelWarn  LogLevel = LogLevel(slog.LevelWarn)
	LevelError LogLevel = LogLevel(slog.LevelError)
)

// Logger is a structured logger with optional Graylog support
type Logger struct {
	logger    *slog.Logger
	graylogMu sync.RWMutex
	graylog   *GraylogClient
	level     LogLevel
	source    string
}

// GraylogClient handles sending logs to Graylog
type GraylogClient struct {
	conn      net.Conn
	source    string
	extra     map[string]interface{}
	mu        sync.Mutex
	connected bool
}

// GELFMessage represents a GELF message structure
type GELFMessage struct {
	Version      string                 `json:"version"`
	Host         string                 `json:"host"`
	ShortMessage string                 `json:"short_message"`
	FullMessage  string                 `json:"full_message,omitempty"`
	Timestamp    float64                `json:"timestamp"`
	Level        int                    `json:"level"`
	Source       string                 `json:"source,omitempty"`
	Extra        map[string]interface{} `json:"-"`
}

// NewGraylogClient creates a new Graylog client
func NewGraylogClient(cfg GraylogConfig) (*GraylogClient, error) {
	var conn net.Conn
	var err error

	protocol := cfg.Protocol
	if protocol == "" {
		protocol = "udp"
	}

	conn, err = net.DialTimeout(protocol, cfg.Endpoint, cfg.Timeout)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to Graylog: %w", err)
	}

	return &GraylogClient{
		conn:      conn,
		source:    cfg.Source,
		extra:     cfg.ExtraFields,
		connected: true,
	}, nil
}

// send sends a GELF message to Graylog
func (g *GraylogClient) send(msg *GELFMessage) error {
	g.mu.Lock()
	defer g.mu.Unlock()

	if !g.connected {
		return nil
	}

	// Add extra fields
	if g.extra != nil {
		msg.Extra = g.extra
	}

	data, err := json.Marshal(msg)
	if err != nil {
		return fmt.Errorf("failed to marshal GELF message: %w", err)
	}

	// Add null byte as delimiter for UDP
	var delimiter byte
	if udpConn, ok := g.conn.(*net.UDPConn); ok {
		delimiter = 0
		_, err = udpConn.Write(append(data, delimiter))
	} else {
		_, err = g.conn.Write(data)
	}

	if err != nil {
		g.connected = false
		return fmt.Errorf("failed to send to Graylog: %w", err)
	}

	return nil
}

// Close closes the Graylog connection
func (g *GraylogClient) Close() error {
	g.mu.Lock()
	defer g.mu.Unlock()

	if g.conn != nil {
		g.connected = false
		return g.conn.Close()
	}
	return nil
}

// NewLogger creates a new logger instance
func NewLogger() *Logger {
	// Create slog handler with JSON output for structured logging
	handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelInfo,
	})

	return &Logger{
		logger: slog.New(handler),
		level:  LevelInfo,
		source: "binlog-sync",
	}
}

// NewLoggerWithLevel creates a new logger with specified level
func NewLoggerWithLevel(level LogLevel) *Logger {
	handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.Level(level),
	})

	return &Logger{
		logger: slog.New(handler),
		level:  level,
		source: "binlog-sync",
	}
}

// SetupGraylog configures Graylog integration
func (l *Logger) SetupGraylog(cfg GraylogConfig) error {
	client, err := NewGraylogClient(cfg)
	if err != nil {
		return err
	}

	l.graylogMu.Lock()
	l.graylog = client
	l.graylogMu.Unlock()

	return nil
}

// With creates a new logger with additional context
func (l *Logger) With(args ...any) *Logger {
	return &Logger{
		logger: l.logger.With(args...),
		level:  l.level,
		source: l.source,
	}
}

// Debug logs a debug message
func (l *Logger) Debug(msg string, args ...any) {
	l.logger.Debug(msg, args...)
	l.sendToGraylog(slog.LevelDebug, msg, args)
}

// Info logs an info message
func (l *Logger) Info(msg string, args ...any) {
	l.logger.Info(msg, args...)
	l.sendToGraylog(slog.LevelInfo, msg, args)
}

// Warn logs a warning message
func (l *Logger) Warn(msg string, args ...any) {
	l.logger.Warn(msg, args...)
	l.sendToGraylog(slog.LevelWarn, msg, args)
}

// Error logs an error message
func (l *Logger) Error(msg string, args ...any) {
	l.logger.Error(msg, args...)
	l.sendToGraylog(slog.LevelError, msg, args)
}

// Fatal logs a fatal message and exits
func (l *Logger) Fatal(msg string, args ...any) {
	l.logger.Error(msg, args...)
	l.sendToGraylog(slog.LevelError, msg, args)
	os.Exit(1)
}

// Debugf logs a formatted debug message
func (l *Logger) Debugf(format string, args ...any) {
	msg := fmt.Sprintf(format, args...)
	l.logger.Debug(msg)
	l.sendToGraylog(slog.LevelDebug, msg, nil)
}

// Infof logs a formatted info message
func (l *Logger) Infof(format string, args ...any) {
	msg := fmt.Sprintf(format, args...)
	l.logger.Info(msg)
	l.sendToGraylog(slog.LevelInfo, msg, nil)
}

// Warnf logs a formatted warning message
func (l *Logger) Warnf(format string, args ...any) {
	msg := fmt.Sprintf(format, args...)
	l.logger.Warn(msg)
	l.sendToGraylog(slog.LevelWarn, msg, nil)
}

// Errorf logs a formatted error message
func (l *Logger) Errorf(format string, args ...any) {
	msg := fmt.Sprintf(format, args...)
	l.logger.Error(msg)
	l.sendToGraylog(slog.LevelError, msg, nil)
}

// Fatalf logs a formatted fatal message and exits
func (l *Logger) Fatalf(format string, args ...any) {
	msg := fmt.Sprintf(format, args...)
	l.logger.Error(msg)
	l.sendToGraylog(slog.LevelError, msg, nil)
	os.Exit(1)
}

// Log logs a message at the specified level
func (l *Logger) Log(level LogLevel, msg string, args ...any) {
	switch level {
	case LevelDebug:
		l.logger.Debug(msg, args...)
	case LevelInfo:
		l.logger.Info(msg, args...)
	case LevelWarn:
		l.logger.Warn(msg, args...)
	case LevelError:
		l.logger.Error(msg, args...)
	}
	l.sendToGraylog(slog.Level(level), msg, args)
}

// sendToGraylog sends a log message to Graylog if configured
func (l *Logger) sendToGraylog(level slog.Level, msg string, args []any) {
	l.graylogMu.RLock()
	defer l.graylogMu.RUnlock()

	if l.graylog == nil {
		return
	}

	// Convert args to structured data
	extra := make(map[string]interface{})
	if args != nil {
		for i := 0; i < len(args)-1; i += 2 {
			if key, ok := args[i].(string); ok {
				extra[key] = args[i+1]
			}
		}
	}

	gelfMsg := &GELFMessage{
		Version:      "1.1",
		Host:         l.source,
		ShortMessage: msg,
		Timestamp:    float64(time.Now().UnixNano()) / 1e9,
		Level:        int(level) + 1, // GELF levels: 1=emerg, 2=alert, 3=crit, 4=err, 5=warn, 6=notice, 7=info, 8=debug
		Source:       l.source,
		Extra:        extra,
	}

	// Send in background to not block
	go func() {
		_ = l.graylog.send(gelfMsg)
	}()
}

// Close closes the logger and any underlying connections
func (l *Logger) Close() {
	l.graylogMu.Lock()
	defer l.graylogMu.Unlock()

	if l.graylog != nil {
		l.graylog.Close()
		l.graylog = nil
	}
}

// GetLogger returns the underlying slog.Logger
func (l *Logger) GetLogger() *slog.Logger {
	return l.logger
}

// Handler returns a slog.Handler for use with other libraries
func (l *Logger) Handler() slog.Handler {
	return l.logger.Handler()
}

// Global logger instance - renamed to avoid conflict with log package
var globalLogger *Logger

// Init initializes the global logger
func InitLogger() {
	globalLogger = NewLogger()
}

// InitLoggerWithLevel initializes the global logger with a specific level
func InitLoggerWithLevel(level LogLevel) {
	globalLogger = NewLoggerWithLevel(level)
}

// SetupGlobalGraylog configures Graylog for the global logger
func SetupGlobalGraylog(cfg GraylogConfig) error {
	return globalLogger.SetupGraylog(cfg)
}

// GetLogger returns the global logger instance
func GetLogger() *Logger {
	if globalLogger == nil {
		InitLogger()
	}
	return globalLogger
}

// Convenience functions using global logger

// Debug logs a debug message
func Debug(msg string, args ...any) {
	GetLogger().Debug(msg, args...)
}

// Info logs an info message
func Info(msg string, args ...any) {
	GetLogger().Info(msg, args...)
}

// Warn logs a warning message
func Warn(msg string, args ...any) {
	GetLogger().Warn(msg, args...)
}

// Error logs an error message
func Error(msg string, args ...any) {
	GetLogger().Error(msg, args...)
}

// Fatal logs a fatal message and exits
func Fatal(msg string, args ...any) {
	GetLogger().Fatal(msg, args...)
}

// Debugf logs a formatted debug message
func Debugf(format string, args ...any) {
	GetLogger().Debugf(format, args...)
}

// Infof logs a formatted info message
func Infof(format string, args ...any) {
	GetLogger().Infof(format, args...)
}

// Warnf logs a formatted warning message
func Warnf(format string, args ...any) {
	GetLogger().Warnf(format, args...)
}

// Errorf logs a formatted error message
func Errorf(format string, args ...any) {
	GetLogger().Errorf(format, args...)
}

// Fatalf logs a formatted fatal message and exits
func Fatalf(format string, args ...any) {
	GetLogger().Fatalf(format, args...)
}

// Log logs a message at the specified level
func Log(level LogLevel, msg string, args ...any) {
	GetLogger().Log(level, msg, args...)
}
