package logging

import (
	"fmt"
	"strings"
	"time"

	"github.com/sirupsen/logrus"
)

const (
	// Colors
	Reset  = "\033[0m"
	Red    = "\033[31m"
	Green  = "\033[32m"
	Yellow = "\033[33m"
	Blue   = "\033[34m"
	Purple = "\033[35m"
	Cyan   = "\033[36m"
	Gray   = "\033[37m"
	Bold   = "\033[1m"

	// Background colors
	BgRed    = "\033[41m"
	BgGreen  = "\033[42m"
	BgYellow = "\033[43m"
	BgBlue   = "\033[44m"
)

// PrettyFormatter implements a beautiful formatter for logrus
type PrettyFormatter struct {
	// TimestampFormat to use for display when a full timestamp is printed
	TimestampFormat string

	// The fields are sorted by default for a consistent output. For applications
	// that log extremely frequently and don't use the JSON formatter this may not
	// be desired.
	DisableSorting bool

	// DisableColors allows users to disable colors when outputting to a TTY
	DisableColors bool
}

// Format implements logrus.Formatter interface
func (f *PrettyFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	var b strings.Builder

	timestampFormat := f.TimestampFormat
	if timestampFormat == "" {
		timestampFormat = "15:04:05.000"
	}

	// Timestamp
	timeStr := entry.Time.Format(timestampFormat)
	if f.DisableColors {
		b.WriteString(fmt.Sprintf("%s", timeStr))
	} else {
		b.WriteString(fmt.Sprintf("%s%s%s", Gray, timeStr, Reset))
	}

	// Level with color
	level := strings.ToUpper(entry.Level.String())
	if !f.DisableColors {
		levelColor := f.getLevelColor(entry.Level)
		b.WriteString(fmt.Sprintf(" [%s%-5s%s]", levelColor, level, Reset))
	} else {
		b.WriteString(fmt.Sprintf(" [%-5s]", level))
	}

	// Build ID (truncated if present)
	if buildID, ok := entry.Data["build_id"]; ok {
		buildStr := f.truncateBuildID(fmt.Sprintf("%v", buildID))
		if !f.DisableColors {
			b.WriteString(fmt.Sprintf(" %s[%s]%s", Purple, buildStr, Reset))
		} else {
			b.WriteString(fmt.Sprintf(" [%s]", buildStr))
		}
	}

	// Main message
	if !f.DisableColors {
		b.WriteString(fmt.Sprintf(" %s%s%s", Bold, entry.Message, Reset))
	} else {
		b.WriteString(fmt.Sprintf(" %s", entry.Message))
	}

	// Additional context from fields
	details := f.formatFields(entry.Data)
	if len(details) > 0 {
		if !f.DisableColors {
			b.WriteString(fmt.Sprintf(" %s(%s)%s", Gray, strings.Join(details, ", "), Reset))
		} else {
			b.WriteString(fmt.Sprintf(" (%s)", strings.Join(details, ", ")))
		}
	}

	// URL on new line if present
	if url, ok := entry.Data["url"]; ok {
		if !f.DisableColors {
			b.WriteString(fmt.Sprintf("\n    %s→%s %v", Gray, Reset, url))
		} else {
			b.WriteString(fmt.Sprintf("\n    → %v", url))
		}
	}

	b.WriteString("\n")
	return []byte(b.String()), nil
}

func (f *PrettyFormatter) getLevelColor(level logrus.Level) string {
	if f.DisableColors {
		return ""
	}

	switch level {
	case logrus.FatalLevel, logrus.PanicLevel:
		return Red + Bold
	case logrus.ErrorLevel:
		return Red
	case logrus.WarnLevel:
		return Yellow
	case logrus.InfoLevel:
		return Green
	case logrus.DebugLevel:
		return Cyan
	default:
		return Reset
	}
}

func (f *PrettyFormatter) truncateBuildID(buildID string) string {
	if len(buildID) > 20 {
		return buildID[:8] + "..." + buildID[len(buildID)-8:]
	}
	return buildID
}

func (f *PrettyFormatter) formatFields(fields logrus.Fields) []string {
	var details []string

	// Skip common fields that are handled separately
	skipFields := map[string]bool{
		"build_id": true,
		"time":     true,
		"level":    true,
		"msg":      true,
		"url":      true,
	}

	// Priority fields to show first
	priorityFields := []string{
		"error", "status", "status_code", "duration", "platform", "build_type",
		"template", "app_id", "app_name", "count", "server", "artifact_size",
		"version", "port", "method", "endpoint",
	}

	// Add priority fields first
	for _, field := range priorityFields {
		if value, ok := fields[field]; ok {
			detail := f.formatField(field, value)
			if detail != "" {
				details = append(details, detail)
			}
		}
	}

	// Add remaining fields
	for key, value := range fields {
		if !skipFields[key] && !f.contains(priorityFields, key) {
			detail := f.formatField(key, value)
			if detail != "" {
				details = append(details, detail)
			}
		}
	}

	return details
}

func (f *PrettyFormatter) formatField(key string, value interface{}) string {
	if f.DisableColors {
		return f.formatFieldNoColor(key, value)
	}

	switch key {
	case "error":
		return fmt.Sprintf("%serror%s=%s%v%s", Red, Reset, Red, value, Reset)
	case "status", "status_code":
		status := fmt.Sprintf("%v", value)
		color := Green
		if strings.Contains(status, "fail") || strings.Contains(status, "error") {
			color = Red
		} else if strings.Contains(status, "pending") || strings.Contains(status, "processing") {
			color = Yellow
		}
		return fmt.Sprintf("%s%s%s=%s%v%s", color, key, Reset, color, value, Reset)
	case "duration":
		duration := f.formatDuration(value)
		if duration != "" {
			return fmt.Sprintf("%sduration%s=%s", Cyan, Reset, duration)
		}
	case "platform":
		return fmt.Sprintf("%splatform%s=%s%v%s", Green, Reset, Green, value, Reset)
	case "build_type":
		color := Yellow
		if fmt.Sprintf("%v", value) == "release" {
			color = Red
		}
		return fmt.Sprintf("%stype%s=%s%v%s", color, Reset, color, value, Reset)
	case "template", "app_id", "app_name":
		return fmt.Sprintf("%s%s%s=%v", Blue, key, Reset, value)
	case "count":
		return fmt.Sprintf("%scount%s=%v", Green, Reset, value)
	case "server":
		return fmt.Sprintf("%sserver%s=%v", Cyan, Reset, value)
	case "artifact_size":
		return fmt.Sprintf("%ssize%s=%s", Purple, Reset, f.formatSize(value))
	case "version":
		return fmt.Sprintf("%sv%s%v%s", Bold, Reset, value, Reset)
	case "port":
		return fmt.Sprintf("%sport%s=%v", Blue, Reset, value)
	case "method":
		return fmt.Sprintf("%s%v%s", Yellow, value, Reset)
	case "endpoint":
		return fmt.Sprintf("%sendpoint%s=%v", Cyan, Reset, value)
	default:
		if value != nil && fmt.Sprintf("%v", value) != "" {
			return fmt.Sprintf("%s=%v", key, value)
		}
	}
	return ""
}

func (f *PrettyFormatter) formatFieldNoColor(key string, value interface{}) string {
	switch key {
	case "duration":
		duration := f.formatDuration(value)
		if duration != "" {
			return fmt.Sprintf("duration=%s", duration)
		}
	case "build_type":
		return fmt.Sprintf("type=%v", value)
	case "artifact_size":
		return fmt.Sprintf("size=%s", f.formatSize(value))
	default:
		if value != nil && fmt.Sprintf("%v", value) != "" {
			return fmt.Sprintf("%s=%v", key, value)
		}
	}
	return ""
}

func (f *PrettyFormatter) formatDuration(value interface{}) string {
	switch v := value.(type) {
	case int64:
		d := time.Duration(v)
		return f.formatDurationValue(d)
	case int:
		d := time.Duration(v)
		return f.formatDurationValue(d)
	case time.Duration:
		return f.formatDurationValue(v)
	case string:
		if d, err := time.ParseDuration(v); err == nil {
			return f.formatDurationValue(d)
		}
	}
	return ""
}

func (f *PrettyFormatter) formatDurationValue(d time.Duration) string {
	if d == 0 {
		return ""
	}

	if d < time.Millisecond {
		return fmt.Sprintf("%.2fμs", float64(d.Nanoseconds())/1000)
	} else if d < time.Second {
		return fmt.Sprintf("%.2fms", float64(d.Nanoseconds())/1e6)
	}
	return fmt.Sprintf("%.2fs", d.Seconds())
}

func (f *PrettyFormatter) formatSize(value interface{}) string {
	var bytes int64
	switch v := value.(type) {
	case int:
		bytes = int64(v)
	case int64:
		bytes = v
	case float64:
		bytes = int64(v)
	case string:
		return v
	default:
		return fmt.Sprintf("%v", value)
	}

	const unit = 1024
	if bytes < unit {
		return fmt.Sprintf("%d B", bytes)
	}
	div, exp := int64(unit), 0
	for n := bytes / unit; n >= unit; n /= unit {
		div *= unit
		exp++
	}
	return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

func (f *PrettyFormatter) contains(slice []string, item string) bool {
	for _, s := range slice {
		if s == item {
			return true
		}
	}
	return false
}
