package logger

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"
)

// LogLevel represents the severity level of a log message
type LogLevel int

const (
	DEBUG LogLevel = iota
	INFO
	WARN
	ERROR
	FATAL
)

var (
	levelStrings = map[LogLevel]string{
		DEBUG: "DEBUG",
		INFO:  "INFO",
		WARN:  "WARN",
		ERROR: "ERROR",
		FATAL: "FATAL",
	}

	stringLevels = map[string]LogLevel{
		"DEBUG": DEBUG,
		"INFO":  INFO,
		"WARN":  WARN,
		"ERROR": ERROR,
		"FATAL": FATAL,
	}
)

// Logger represents a structured logger instance
type Logger struct {
	serviceName string
	level       LogLevel
	output      *log.Logger
	mu          sync.Mutex
	jsonFormat  bool

	logDir string
}

// LogEntry represents a structured log entry
type LogEntry struct {
	Timestamp     string                 `json:"timestamp"`
	Level         string                 `json:"level"`
	Service       string                 `json:"service"`
	Message       string                 `json:"message"`
	RequestID     string                 `json:"request_id,omitempty"`
	CorrelationID string                 `json:"correlation_id,omitempty"`
	File          string                 `json:"file,omitempty"`
	Line          int                    `json:"line,omitempty"`
	Duration      string                 `json:"duration,omitempty"`
	Fields        map[string]interface{} `json:"fields,omitempty"`
}

// New creates a new logger instance
func New(serviceName string, level LogLevel, jsonFormat bool, logDir ...string) *Logger {
	// Tentukan direktori log berdasarkan prioritas:
	// 1. Parameter logDir (jika disediakan)
	// 2. Environment variable LOG_DIR (jika ada)
	// 3. Default ke pkg/logger/data relatif terhadap root proyek

	var finalLogDir string

	// Cek apakah logDir disediakan sebagai parameter
	if len(logDir) > 0 && logDir[0] != "" {
		finalLogDir = logDir[0]
	} else {
		// Cek environment variable
		if envLogDir := os.Getenv("LOG_DIR"); envLogDir != "" {
			finalLogDir = envLogDir
		} else {
			// Default: dapatkan path relatif terhadap root proyek
			// Dapatkan path executable
			exePath, err := os.Executable()
			if err != nil {
				// Fallback ke current working directory jika gagal
				finalLogDir = filepath.Join(".", "pkg", "logger", "data")
			} else {
				// Dapatkan direktori executable
				exeDir := filepath.Dir(exePath)

				// Jika berjalan dengan go run, executable ada di temp directory
				// Coba dapatkan path source code
				if strings.Contains(exeDir, "go-build") || strings.Contains(exeDir, "tmp") {
					// Gunakan runtime.Caller untuk mendapatkan path source
					_, file, _, ok := runtime.Caller(0)
					if ok {
						// Dapatkan direktori source (2 level up dari pkg/logger)
						sourceDir := filepath.Dir(file)
						for i := 0; i < 3; i++ { // Naik 3 level ke root proyek
							sourceDir = filepath.Dir(sourceDir)
						}
						finalLogDir = filepath.Join(sourceDir, "pkg", "logger", "data")
					} else {
						// Fallback
						finalLogDir = filepath.Join(".", "pkg", "logger", "data")
					}
				} else {
					// Untuk binary yang sudah dikompilasi, asumsikan struktur proyek
					finalLogDir = filepath.Join(exeDir, "pkg", "logger", "data")
				}
			}
		}
	}

	// Konversi ke path absolut
	absPath, err := filepath.Abs(finalLogDir)
	if err == nil {
		finalLogDir = absPath
	}

	// Buat direktori jika belum ada
	if err := os.MkdirAll(finalLogDir, 0755); err != nil {
		// Fallback ke stdout jika gagal membuat direktori
		fmt.Printf("Warning: Failed to create log directory %s: %v\n", finalLogDir, err)
		return &Logger{
			serviceName: serviceName,
			level:       level,
			output:      log.New(os.Stdout, "", 0),
			jsonFormat:  jsonFormat,
			logDir:      "", // Kosongkan karena gagal
		}
	}

	return &Logger{
		serviceName: serviceName,
		level:       level,
		output:      log.New(os.Stdout, "", 0),
		jsonFormat:  jsonFormat,
		logDir:      finalLogDir,
	}
}

// NewFromConfig creates a new logger from configuration
func NewFromConfig(cfg Config) *Logger {
	level := INFO
	if l, exists := stringLevels[strings.ToUpper(cfg.Level)]; exists {
		level = l
	}

	return New(cfg.Service, level, cfg.JSONFormat)
}

// Default creates a default logger instance
func Default() *Logger {
	return New("api-service", INFO, false)
}

// WithService returns a new logger with the specified service name
func (l *Logger) WithService(serviceName string) *Logger {
	return &Logger{
		serviceName: serviceName,
		level:       l.level,
		output:      l.output,
		jsonFormat:  l.jsonFormat,
		logDir:      l.logDir,
	}
}

// SetLevel sets the log level for the logger
func (l *Logger) SetLevel(level LogLevel) {
	l.mu.Lock()
	defer l.mu.Unlock()
	l.level = level
}

// SetJSONFormat sets whether to output logs in JSON format
func (l *Logger) SetJSONFormat(jsonFormat bool) {
	l.mu.Lock()
	defer l.mu.Unlock()
	l.jsonFormat = jsonFormat
}

// Debug logs a debug message
func (l *Logger) Debug(msg string, fields ...map[string]interface{}) {
	l.log(DEBUG, msg, nil, fields...)
}

// Debugf logs a formatted debug message
func (l *Logger) Debugf(format string, args ...interface{}) {
	l.log(DEBUG, fmt.Sprintf(format, args...), nil)
}

// Info logs an info message
func (l *Logger) Info(msg string, fields ...map[string]interface{}) {
	l.log(INFO, msg, nil, fields...)
}

// Infof logs a formatted info message
func (l *Logger) Infof(format string, args ...interface{}) {
	l.log(INFO, fmt.Sprintf(format, args...), nil)
}

// Warn logs a warning message
func (l *Logger) Warn(msg string, fields ...map[string]interface{}) {
	l.log(WARN, msg, nil, fields...)
}

// Warnf logs a formatted warning message
func (l *Logger) Warnf(format string, args ...interface{}) {
	l.log(WARN, fmt.Sprintf(format, args...), nil)
}

// Error logs an error message
func (l *Logger) Error(msg string, fields ...map[string]interface{}) {
	l.log(ERROR, msg, nil, fields...)
}

// Errorf logs a formatted error message
func (l *Logger) Errorf(format string, args ...interface{}) {
	l.log(ERROR, fmt.Sprintf(format, args...), nil)
}

// Fatal logs a fatal message and exits the program
func (l *Logger) Fatal(msg string, fields ...map[string]interface{}) {
	l.log(FATAL, msg, nil, fields...)
	os.Exit(1)
}

// Fatalf logs a formatted fatal message and exits the program
func (l *Logger) Fatalf(format string, args ...interface{}) {
	l.log(FATAL, fmt.Sprintf(format, args...), nil)
	os.Exit(1)
}

// WithRequestID returns a new logger with the specified request ID
func (l *Logger) WithRequestID(requestID string) *Logger {
	return l.withField("request_id", requestID)
}

// WithCorrelationID returns a new logger with the specified correlation ID
func (l *Logger) WithCorrelationID(correlationID string) *Logger {
	return l.withField("correlation_id", correlationID)
}

// WithField returns a new logger with an additional field
func (l *Logger) WithField(key string, value interface{}) *Logger {
	return l.withField(key, value)
}

// WithFields returns a new logger with additional fields
func (l *Logger) WithFields(fields map[string]interface{}) *Logger {
	return &Logger{
		serviceName: l.serviceName,
		level:       l.level,
		output:      l.output,
		jsonFormat:  l.jsonFormat,
		logDir:      l.logDir,
	}
}

// LogDuration logs the duration of an operation
func (l *Logger) LogDuration(start time.Time, operation string, fields ...map[string]interface{}) {
	duration := time.Since(start)
	l.Info(fmt.Sprintf("%s completed", operation), append(fields, map[string]interface{}{
		"duration":    duration.String(),
		"duration_ms": duration.Milliseconds(),
	})...)
}

// log is the internal logging method
func (l *Logger) log(level LogLevel, msg string, duration *time.Duration, fields ...map[string]interface{}) {
	if level < l.level {
		return
	}

	// Get caller information
	_, file, line, ok := runtime.Caller(3) // Adjust caller depth
	var callerFile string
	var callerLine int
	if ok {
		// Shorten file path
		parts := strings.Split(file, "/")
		if len(parts) > 2 {
			callerFile = strings.Join(parts[len(parts)-2:], "/")
		} else {
			callerFile = file
		}
		callerLine = line
	}

	// Merge all fields
	mergedFields := make(map[string]interface{})
	for _, f := range fields {
		for k, v := range f {
			mergedFields[k] = v
		}
	}

	entry := LogEntry{
		Timestamp: time.Now().Format(time.RFC3339),
		Level:     levelStrings[level],
		Service:   l.serviceName,
		Message:   msg,
		File:      callerFile,
		Line:      callerLine,
		Fields:    mergedFields,
	}

	if duration != nil {
		entry.Duration = duration.String()
	}

	if l.jsonFormat {
		l.outputJSON(entry)
	} else {
		l.outputText(entry)
	}

	if level == FATAL {
		os.Exit(1)
	}
}

// outputJSON outputs the log entry in JSON format
func (l *Logger) outputJSON(entry LogEntry) {
	jsonData, err := json.Marshal(entry)
	if err != nil {
		// Fallback to text output if JSON marshaling fails
		l.outputText(entry)
		return
	}
	l.output.Println(string(jsonData))
}

// outputText outputs the log entry in text format
func (l *Logger) outputText(entry LogEntry) {
	timestamp := entry.Timestamp
	level := entry.Level
	service := entry.Service
	message := entry.Message

	// Base log line
	logLine := fmt.Sprintf("%s [%s] %s: %s", timestamp, level, service, message)

	// Add file and line if available
	if entry.File != "" && entry.Line > 0 {
		logLine += fmt.Sprintf(" (%s:%d)", entry.File, entry.Line)
	}

	// Add request ID if available
	if entry.RequestID != "" {
		logLine += fmt.Sprintf(" [req:%s]", entry.RequestID)
	}

	// Add correlation ID if available
	if entry.CorrelationID != "" {
		logLine += fmt.Sprintf(" [corr:%s]", entry.CorrelationID)
	}

	// Add duration if available
	if entry.Duration != "" {
		logLine += fmt.Sprintf(" [dur:%s]", entry.Duration)
	}

	// Add additional fields
	if len(entry.Fields) > 0 {
		fields := make([]string, 0, len(entry.Fields))
		for k, v := range entry.Fields {
			fields = append(fields, fmt.Sprintf("%s=%v", k, v))
		}
		logLine += " [" + strings.Join(fields, " ") + "]"
	}

	l.output.Println(logLine)
}

// withField creates a new logger with an additional field
func (l *Logger) withField(key string, value interface{}) *Logger {
	return &Logger{
		serviceName: l.serviceName,
		level:       l.level,
		output:      l.output,
		jsonFormat:  l.jsonFormat,
		logDir:      l.logDir,
	}
}

// String returns the string representation of a log level
func (l LogLevel) String() string {
	return levelStrings[l]
}

// ParseLevel parses a string into a LogLevel
func ParseLevel(level string) (LogLevel, error) {
	if l, exists := stringLevels[strings.ToUpper(level)]; exists {
		return l, nil
	}
	return INFO, fmt.Errorf("invalid log level: %s", level)
}

// Global logger instance
var globalLogger = Default()

// SetGlobalLogger sets the global logger instance
func SetGlobalLogger(logger *Logger) {
	globalLogger = logger
}

// Global logging functions
func Debug(msg string, fields ...map[string]interface{}) {
	globalLogger.Debug(msg, fields...)
}

func Debugf(format string, args ...interface{}) {
	globalLogger.Debugf(format, args...)
}

func Info(msg string, fields ...map[string]interface{}) {
	globalLogger.Info(msg, fields...)
}

func Infof(format string, args ...interface{}) {
	globalLogger.Infof(format, args...)
}

func Warn(msg string, fields ...map[string]interface{}) {
	globalLogger.Warn(msg, fields...)
}

func Warnf(format string, args ...interface{}) {
	globalLogger.Warnf(format, args...)
}

func Error(msg string, fields ...map[string]interface{}) {
	globalLogger.Error(msg, fields...)
}

func Errorf(format string, args ...interface{}) {
	globalLogger.Errorf(format, args...)
}

func Fatal(msg string, fields ...map[string]interface{}) {
	globalLogger.Fatal(msg, fields...)
}

func Fatalf(format string, args ...interface{}) {
	globalLogger.Fatalf(format, args...)
}

// SaveLogText menyimpan log dalam format teks dengan pemisah |
func (l *Logger) SaveLogText(entry LogEntry) error {
	// Format log dengan pemisah |
	logLine := fmt.Sprintf("%s|%s|%s|%s|%s|%s|%s|%s:%d",
		entry.Timestamp,
		entry.Level,
		entry.Service,
		entry.Message,
		entry.RequestID,
		entry.CorrelationID,
		entry.Duration,
		entry.File,
		entry.Line)

	// Tambahkan fields jika ada
	if len(entry.Fields) > 0 {
		fieldsStr := ""
		for k, v := range entry.Fields {
			fieldsStr += fmt.Sprintf("|%s=%v", k, v)
		}
		logLine += fieldsStr
	}
	logLine += "\n"

	// Buat direktori jika belum ada
	if err := os.MkdirAll(l.logDir, 0755); err != nil {
		return err
	}

	// Tulis ke file dengan mutex lock untuk concurrency safety
	l.mu.Lock()
	defer l.mu.Unlock()

	filePath := filepath.Join(l.logDir, "logs.txt")
	f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	if _, err := f.WriteString(logLine); err != nil {
		return err
	}
	return nil
}

// SaveLogJSON menyimpan log dalam format JSON
func (l *Logger) SaveLogJSON(entry LogEntry) error {
	jsonData, err := json.Marshal(entry)
	if err != nil {
		return err
	}

	// Buat direktori jika belum ada
	if err := os.MkdirAll(l.logDir, 0755); err != nil {
		return err
	}

	// Tulis ke file dengan mutex lock for concurrency safety
	l.mu.Lock()
	defer l.mu.Unlock()

	filePath := filepath.Join(l.logDir, "logs.json")
	f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	if _, err := f.WriteString(string(jsonData) + "\n"); err != nil {
		return err
	}
	return nil
}

// SaveLogToDatabase menyimpan log ke database
func (l *Logger) SaveLogToDatabase(entry LogEntry) error {
	// Implementasi penyimpanan ke database
	// Ini adalah contoh implementasi, sesuaikan dengan struktur database Anda

	// Untuk saat ini, kita akan simpan ke file sebagai placeholder
	// Anda dapat mengganti ini dengan koneksi database yang sesuai
	dbLogLine := fmt.Sprintf("DB_LOG: %s|%s|%s|%s\n",
		entry.Timestamp, entry.Level, entry.Service, entry.Message)

	if err := os.MkdirAll(l.logDir, 0755); err != nil {
		return err
	}

	// Tulis ke file dengan mutex lock for concurrency safety
	l.mu.Lock()
	defer l.mu.Unlock()

	filePath := filepath.Join(l.logDir, "database_logs.txt")
	f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	if _, err := f.WriteString(dbLogLine); err != nil {
		return err
	}
	return nil
}

// LogAndSave melakukan logging dan menyimpan ke semua format
func (l *Logger) LogAndSave(level LogLevel, msg string, fields ...map[string]interface{}) {
	// Panggil fungsi log biasa
	l.log(level, msg, nil, fields...)

	// Dapatkan entry log yang baru dibuat
	_, file, line, ok := runtime.Caller(2)
	var callerFile string
	var callerLine int
	if ok {
		parts := strings.Split(file, "/")
		if len(parts) > 2 {
			callerFile = strings.Join(parts[len(parts)-2:], "/")
		} else {
			callerFile = file
		}
		callerLine = line
	}

	mergedFields := make(map[string]interface{})
	for _, f := range fields {
		for k, v := range f {
			mergedFields[k] = v
		}
	}

	entry := LogEntry{
		Timestamp: time.Now().Format(time.RFC3339),
		Level:     levelStrings[level],
		Service:   l.serviceName,
		Message:   msg,
		File:      callerFile,
		Line:      callerLine,
		Fields:    mergedFields,
	}

	// Simpan ke semua format
	go func() {
		l.SaveLogText(entry)
		l.SaveLogJSON(entry)
		l.SaveLogToDatabase(entry)
	}()
}

// Global fungsi untuk menyimpan log
func SaveLogText(entry LogEntry) error {
	return globalLogger.SaveLogText(entry)
}

func SaveLogJSON(entry LogEntry) error {
	return globalLogger.SaveLogJSON(entry)
}

func SaveLogToDatabase(entry LogEntry) error {
	return globalLogger.SaveLogToDatabase(entry)
}
