package logger

import (
	"io"
	"os"
	"path/filepath"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

// Logger interface defines logging methods
type Logger interface {
	Debug(msg string, fields map[string]interface{})
	Info(msg string, fields map[string]interface{})
	Warn(msg string, fields map[string]interface{})
	Error(msg string, fields map[string]interface{})
	Fatal(msg string, fields map[string]interface{})
	WithFields(fields map[string]interface{}) Logger
	Sync() error // Flush any buffered log entries
}

// ZapLogger wraps zap logger
type ZapLogger struct {
	logger *zap.Logger
	sugar  *zap.SugaredLogger
	fields map[string]interface{}
}

type Config struct {
	Level  string     `mapstructure:"level"`
	Format string     `mapstructure:"format"`
	Output string     `mapstructure:"output"`
	File   FileConfig `mapstructure:"file"`
}

type FileConfig struct {
	Path       string `mapstructure:"path"`
	MaxSize    int    `mapstructure:"max_size"` // MB
	MaxBackups int    `mapstructure:"max_backups"`
	MaxAge     int    `mapstructure:"max_age"` // days
	Compress   bool   `mapstructure:"compress"`
}

// NewLogger creates a new logger instance with Zap and Lumberjack
func NewLogger(config *Config) Logger {
	// Parse log level
	level := parseLogLevel(config.Level)

	// Create encoder config
	encoderConfig := createEncoderConfig(config.Format)

	// Create encoder
	encoder := createEncoder(config.Format, encoderConfig)

	// Create writer
	writer := createWriter(config)

	// Create core
	core := zapcore.NewCore(encoder, writer, level)

	// Create logger with caller information
	logger := zap.New(core,
		zap.AddCaller(),
		zap.AddCallerSkip(1),
		zap.AddStacktrace(zapcore.ErrorLevel),
	)

	return &ZapLogger{
		logger: logger,
		sugar:  logger.Sugar(),
		fields: make(map[string]interface{}),
	}
}

// createWriter creates the appropriate writer based on configuration
func createWriter(config *Config) zapcore.WriteSyncer {
	switch config.Output {
	case "stdout":
		return zapcore.AddSync(os.Stdout)
	case "stderr":
		return zapcore.AddSync(os.Stderr)
	case "file":
		return createFileWriter(config.File)
	default:
		// Default to stdout
		return zapcore.AddSync(os.Stdout)
	}
}

// createFileWriter creates a file writer with rotation using lumberjack
func createFileWriter(fileConfig FileConfig) zapcore.WriteSyncer {
	// Ensure log directory exists
	logDir := filepath.Dir(fileConfig.Path)
	if err := os.MkdirAll(logDir, 0755); err != nil {
		// Fallback to stdout if can't create directory
		return zapcore.AddSync(os.Stdout)
	}

	// Create lumberjack logger for rotation
	lumberjackLogger := &lumberjack.Logger{
		Filename:   fileConfig.Path,
		MaxSize:    fileConfig.MaxSize, // MB
		MaxBackups: fileConfig.MaxBackups,
		MaxAge:     fileConfig.MaxAge, // days
		Compress:   fileConfig.Compress,
	}

	// Create multi-writer to write to both file and stdout
	multiWriter := io.MultiWriter(lumberjackLogger, os.Stdout)

	return zapcore.AddSync(multiWriter)
}

// createEncoderConfig creates encoder configuration
func createEncoderConfig(format string) zapcore.EncoderConfig {
	config := zap.NewProductionEncoderConfig()

	// Customize timestamp format
	config.TimeKey = "timestamp"
	config.EncodeTime = zapcore.ISO8601TimeEncoder

	// Customize level encoding
	config.EncodeLevel = zapcore.LowercaseLevelEncoder

	// Customize caller encoding
	config.EncodeCaller = zapcore.ShortCallerEncoder

	// Customize duration encoding
	config.EncodeDuration = zapcore.StringDurationEncoder

	return config
}

// createEncoder creates the appropriate encoder
func createEncoder(format string, config zapcore.EncoderConfig) zapcore.Encoder {
	switch format {
	case "json":
		return zapcore.NewJSONEncoder(config)
	case "console", "text":
		return zapcore.NewConsoleEncoder(config)
	default:
		return zapcore.NewJSONEncoder(config)
	}
}

// parseLogLevel converts string level to zapcore.Level
func parseLogLevel(level string) zapcore.Level {
	switch level {
	case "debug":
		return zapcore.DebugLevel
	case "info":
		return zapcore.InfoLevel
	case "warn", "warning":
		return zapcore.WarnLevel
	case "error":
		return zapcore.ErrorLevel
	case "fatal":
		return zapcore.FatalLevel
	case "panic":
		return zapcore.PanicLevel
	default:
		return zapcore.InfoLevel
	}
}

// convertFields converts map[string]interface{} to zap fields
func convertFields(fields map[string]interface{}) []zap.Field {
	zapFields := make([]zap.Field, 0, len(fields))
	for key, value := range fields {
		zapFields = append(zapFields, zap.Any(key, value))
	}
	return zapFields
}

// Debug logs a debug message
func (l *ZapLogger) Debug(msg string, fields map[string]interface{}) {
	allFields := l.mergeFields(fields)
	l.logger.Debug(msg, convertFields(allFields)...)
}

// Info logs an info message
func (l *ZapLogger) Info(msg string, fields map[string]interface{}) {
	allFields := l.mergeFields(fields)
	l.logger.Info(msg, convertFields(allFields)...)
}

// Warn logs a warning message
func (l *ZapLogger) Warn(msg string, fields map[string]interface{}) {
	allFields := l.mergeFields(fields)
	l.logger.Warn(msg, convertFields(allFields)...)
}

// Error logs an error message
func (l *ZapLogger) Error(msg string, fields map[string]interface{}) {
	allFields := l.mergeFields(fields)
	l.logger.Error(msg, convertFields(allFields)...)
}

// Fatal logs a fatal message and exits
func (l *ZapLogger) Fatal(msg string, fields map[string]interface{}) {
	allFields := l.mergeFields(fields)
	l.logger.Fatal(msg, convertFields(allFields)...)
}

// WithFields returns a logger with predefined fields
func (l *ZapLogger) WithFields(fields map[string]interface{}) Logger {
	newFields := make(map[string]interface{})

	// Copy existing fields
	for k, v := range l.fields {
		newFields[k] = v
	}

	// Add new fields
	for k, v := range fields {
		newFields[k] = v
	}

	return &ZapLogger{
		logger: l.logger,
		sugar:  l.sugar,
		fields: newFields,
	}
}

// Sync flushes any buffered log entries
func (l *ZapLogger) Sync() error {
	return l.logger.Sync()
}

// mergeFields merges instance fields with provided fields
func (l *ZapLogger) mergeFields(fields map[string]interface{}) map[string]interface{} {
	if len(l.fields) == 0 {
		return fields
	}

	merged := make(map[string]interface{})

	// Add instance fields first
	for k, v := range l.fields {
		merged[k] = v
	}

	// Add provided fields (they override instance fields)
	for k, v := range fields {
		merged[k] = v
	}

	return merged
}
