package leader

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strings"
	"sync"
	"time"
)

// LogLevel represents different logging levels
type LogLevel int

const (
	LogLevelDebug LogLevel = iota
	LogLevelInfo
	LogLevelWarn
	LogLevelError
	LogLevelCritical
)

// String returns string representation of log level
func (ll LogLevel) String() string {
	switch ll {
	case LogLevelDebug:
		return "DEBUG"
	case LogLevelInfo:
		return "INFO"
	case LogLevelWarn:
		return "WARN"
	case LogLevelError:
		return "ERROR"
	case LogLevelCritical:
		return "CRITICAL"
	default:
		return "UNKNOWN"
	}
}

// ContextLogger provides structured logging for context operations
type ContextLogger struct {
	mu          sync.RWMutex
	level       LogLevel
	outputs     []LogOutput
	fields      map[string]interface{}
	nodeID      string
	component   string
}

// LogOutput represents a logging output destination
type LogOutput interface {
	Write(entry *LogEntry) error
	Close() error
}

// LogEntry represents a single log entry
type LogEntry struct {
	Timestamp   time.Time              `json:"timestamp"`
	Level       LogLevel               `json:"level"`
	Message     string                 `json:"message"`
	Component   string                 `json:"component"`
	NodeID      string                 `json:"node_id"`
	Fields      map[string]interface{} `json:"fields"`
	Context     map[string]string      `json:"context,omitempty"`
	RequestID   string                 `json:"request_id,omitempty"`
	JobID       string                 `json:"job_id,omitempty"`
	ElectionTerm int64                 `json:"election_term,omitempty"`
	StackTrace  string                 `json:"stack_trace,omitempty"`
}

// NewContextLogger creates a new context logger
func NewContextLogger(nodeID, component string, level LogLevel) *ContextLogger {
	logger := &ContextLogger{
		level:     level,
		fields:    make(map[string]interface{}),
		nodeID:    nodeID,
		component: component,
		outputs:   make([]LogOutput, 0),
	}
	
	// Add default console output
	logger.AddOutput(NewConsoleOutput())
	
	return logger
}

// SetLevel sets the logging level
func (cl *ContextLogger) SetLevel(level LogLevel) {
	cl.mu.Lock()
	defer cl.mu.Unlock()
	cl.level = level
}

// AddOutput adds a log output destination
func (cl *ContextLogger) AddOutput(output LogOutput) {
	cl.mu.Lock()
	defer cl.mu.Unlock()
	cl.outputs = append(cl.outputs, output)
}

// WithField adds a field to all subsequent log entries
func (cl *ContextLogger) WithField(key string, value interface{}) *ContextLogger {
	cl.mu.Lock()
	defer cl.mu.Unlock()
	
	newLogger := &ContextLogger{
		level:     cl.level,
		fields:    make(map[string]interface{}),
		nodeID:    cl.nodeID,
		component: cl.component,
		outputs:   cl.outputs,
	}
	
	// Copy existing fields
	for k, v := range cl.fields {
		newLogger.fields[k] = v
	}
	
	// Add new field
	newLogger.fields[key] = value
	
	return newLogger
}

// WithFields adds multiple fields to all subsequent log entries
func (cl *ContextLogger) WithFields(fields map[string]interface{}) *ContextLogger {
	cl.mu.Lock()
	defer cl.mu.Unlock()
	
	newLogger := &ContextLogger{
		level:     cl.level,
		fields:    make(map[string]interface{}),
		nodeID:    cl.nodeID,
		component: cl.component,
		outputs:   cl.outputs,
	}
	
	// Copy existing fields
	for k, v := range cl.fields {
		newLogger.fields[k] = v
	}
	
	// Add new fields
	for k, v := range fields {
		newLogger.fields[k] = v
	}
	
	return newLogger
}

// WithContext creates a logger with context information
func (cl *ContextLogger) WithContext(ctx context.Context) *ContextLogger {
	// Extract context values if present
	fields := make(map[string]interface{})
	
	if requestID := ctx.Value("request_id"); requestID != nil {
		fields["request_id"] = requestID
	}
	if jobID := ctx.Value("job_id"); jobID != nil {
		fields["job_id"] = jobID
	}
	if term := ctx.Value("election_term"); term != nil {
		fields["election_term"] = term
	}
	
	return cl.WithFields(fields)
}

// Debug logs a debug message
func (cl *ContextLogger) Debug(message string, args ...interface{}) {
	cl.log(LogLevelDebug, message, args...)
}

// Info logs an info message
func (cl *ContextLogger) Info(message string, args ...interface{}) {
	cl.log(LogLevelInfo, message, args...)
}

// Warn logs a warning message
func (cl *ContextLogger) Warn(message string, args ...interface{}) {
	cl.log(LogLevelWarn, message, args...)
}

// Error logs an error message
func (cl *ContextLogger) Error(message string, args ...interface{}) {
	cl.log(LogLevelError, message, args...)
}

// Critical logs a critical message
func (cl *ContextLogger) Critical(message string, args ...interface{}) {
	cl.log(LogLevelCritical, message, args...)
}

// LogContextGeneration logs context generation events
func (cl *ContextLogger) LogContextGeneration(event string, req *ContextGenerationRequest, job *ContextGenerationJob, err error) {
	fields := map[string]interface{}{
		"event": event,
	}
	
	if req != nil {
		fields["request_id"] = req.ID
		fields["ucxl_address"] = req.UCXLAddress.String()
		fields["file_path"] = req.FilePath
		fields["role"] = req.Role
		fields["priority"] = req.Priority.String()
		fields["requested_by"] = req.RequestedBy
	}
	
	if job != nil {
		fields["job_id"] = job.ID
		fields["job_status"] = job.Status
		fields["started_at"] = job.StartedAt
		if job.CompletedAt != nil {
			fields["completed_at"] = *job.CompletedAt
			fields["duration"] = job.CompletedAt.Sub(job.StartedAt)
		}
		fields["progress"] = job.Progress
		fields["node_id"] = job.NodeID
	}
	
	logger := cl.WithFields(fields)
	
	if err != nil {
		logger.Error("Context generation event: %s - Error: %v", event, err)
	} else {
		logger.Info("Context generation event: %s", event)
	}
}

// LogLeadershipChange logs leadership change events
func (cl *ContextLogger) LogLeadershipChange(event, oldLeader, newLeader string, term int64, metadata map[string]interface{}) {
	fields := map[string]interface{}{
		"event":      event,
		"old_leader": oldLeader,
		"new_leader": newLeader,
		"term":       term,
	}
	
	// Add metadata
	for k, v := range metadata {
		fields[k] = v
	}
	
	logger := cl.WithFields(fields)
	logger.Info("Leadership change: %s", event)
}

// LogElectionEvent logs election-related events
func (cl *ContextLogger) LogElectionEvent(event string, term int64, candidates []string, winner string, metadata map[string]interface{}) {
	fields := map[string]interface{}{
		"event":      event,
		"term":       term,
		"candidates": candidates,
		"winner":     winner,
	}
	
	// Add metadata
	for k, v := range metadata {
		fields[k] = v
	}
	
	logger := cl.WithFields(fields)
	logger.Info("Election event: %s", event)
}

// LogFailoverEvent logs failover events
func (cl *ContextLogger) LogFailoverEvent(event, oldLeader, newLeader string, duration time.Duration, success bool, issues []string) {
	fields := map[string]interface{}{
		"event":      event,
		"old_leader": oldLeader,
		"new_leader": newLeader,
		"duration":   duration,
		"success":    success,
		"issues":     issues,
	}
	
	logger := cl.WithFields(fields)
	
	if success {
		logger.Info("Failover event: %s", event)
	} else {
		logger.Error("Failover event: %s - Failed with issues: %v", event, issues)
	}
}

// LogHealthEvent logs health monitoring events
func (cl *ContextLogger) LogHealthEvent(event string, nodeID string, healthScore float64, status HealthStatus, issues []string) {
	fields := map[string]interface{}{
		"event":        event,
		"node_id":      nodeID,
		"health_score": healthScore,
		"status":       status,
		"issues":       issues,
	}
	
	logger := cl.WithFields(fields)
	
	switch status {
	case HealthStatusHealthy:
		logger.Debug("Health event: %s", event)
	case HealthStatusDegraded:
		logger.Warn("Health event: %s - Node degraded", event)
	case HealthStatusUnhealthy:
		logger.Error("Health event: %s - Node unhealthy: %v", event, issues)
	case HealthStatusCritical:
		logger.Critical("Health event: %s - Node critical: %v", event, issues)
	}
}

// LogMetrics logs metrics information
func (cl *ContextLogger) LogMetrics(metrics *ContextMetrics) {
	fields := map[string]interface{}{
		"uptime":             metrics.Uptime,
		"total_requests":     metrics.TotalRequests,
		"success_rate":       metrics.SuccessRate,
		"throughput":         metrics.Throughput,
		"average_latency":    metrics.AverageLatency,
		"queue_length":       metrics.MaxQueueLength,
		"leadership_changes": metrics.LeadershipChanges,
	}
	
	logger := cl.WithFields(fields)
	logger.Debug("Context generation metrics")
}

// log is the internal logging method
func (cl *ContextLogger) log(level LogLevel, message string, args ...interface{}) {
	cl.mu.RLock()
	defer cl.mu.RUnlock()
	
	// Check if level is enabled
	if level < cl.level {
		return
	}
	
	// Format message
	formattedMessage := message
	if len(args) > 0 {
		formattedMessage = fmt.Sprintf(message, args...)
	}
	
	// Create log entry
	entry := &LogEntry{
		Timestamp: time.Now(),
		Level:     level,
		Message:   formattedMessage,
		Component: cl.component,
		NodeID:    cl.nodeID,
		Fields:    make(map[string]interface{}),
	}
	
	// Copy fields
	for k, v := range cl.fields {
		entry.Fields[k] = v
	}
	
	// Write to all outputs
	for _, output := range cl.outputs {
		if err := output.Write(entry); err != nil {
			// Fallback to standard log if output fails
			log.Printf("Failed to write log entry: %v", err)
		}
	}
}

// Close closes all log outputs
func (cl *ContextLogger) Close() error {
	cl.mu.Lock()
	defer cl.mu.Unlock()
	
	var errors []string
	for _, output := range cl.outputs {
		if err := output.Close(); err != nil {
			errors = append(errors, err.Error())
		}
	}
	
	if len(errors) > 0 {
		return fmt.Errorf("errors closing log outputs: %s", strings.Join(errors, ", "))
	}
	
	return nil
}

// ConsoleOutput writes logs to console
type ConsoleOutput struct {
	colorize bool
}

// NewConsoleOutput creates a new console output
func NewConsoleOutput() *ConsoleOutput {
	return &ConsoleOutput{
		colorize: true, // TODO: Detect if terminal supports colors
	}
}

// Write writes a log entry to console
func (co *ConsoleOutput) Write(entry *LogEntry) error {
	var levelPrefix string
	if co.colorize {
		switch entry.Level {
		case LogLevelDebug:
			levelPrefix = "\033[36mDEBUG\033[0m"  // Cyan
		case LogLevelInfo:
			levelPrefix = "\033[32mINFO\033[0m"   // Green
		case LogLevelWarn:
			levelPrefix = "\033[33mWARN\033[0m"   // Yellow
		case LogLevelError:
			levelPrefix = "\033[31mERROR\033[0m"  // Red
		case LogLevelCritical:
			levelPrefix = "\033[35mCRIT\033[0m"   // Magenta
		}
	} else {
		levelPrefix = entry.Level.String()
	}
	
	timestamp := entry.Timestamp.Format("2006-01-02 15:04:05.000")
	
	// Format basic log line
	logLine := fmt.Sprintf("%s [%s] [%s:%s] %s",
		timestamp,
		levelPrefix,
		entry.Component,
		entry.NodeID,
		entry.Message,
	)
	
	// Add fields if any
	if len(entry.Fields) > 0 {
		if fieldsJSON, err := json.Marshal(entry.Fields); err == nil {
			logLine += fmt.Sprintf(" | %s", string(fieldsJSON))
		}
	}
	
	fmt.Println(logLine)
	return nil
}

// Close closes the console output (no-op)
func (co *ConsoleOutput) Close() error {
	return nil
}

// FileOutput writes logs to a file
type FileOutput struct {
	mu       sync.Mutex
	file     *os.File
	filename string
}

// NewFileOutput creates a new file output
func NewFileOutput(filename string) (*FileOutput, error) {
	file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
	if err != nil {
		return nil, err
	}
	
	return &FileOutput{
		file:     file,
		filename: filename,
	}, nil
}

// Write writes a log entry to file
func (fo *FileOutput) Write(entry *LogEntry) error {
	fo.mu.Lock()
	defer fo.mu.Unlock()
	
	// Convert to JSON
	entryJSON, err := json.Marshal(entry)
	if err != nil {
		return err
	}
	
	// Write to file with newline
	_, err = fo.file.Write(append(entryJSON, '\n'))
	return err
}

// Close closes the file output
func (fo *FileOutput) Close() error {
	fo.mu.Lock()
	defer fo.mu.Unlock()
	
	if fo.file != nil {
		err := fo.file.Close()
		fo.file = nil
		return err
	}
	
	return nil
}

// Priority extension for logging
func (p Priority) String() string {
	switch p {
	case PriorityLow:
		return "low"
	case PriorityNormal:
		return "normal"
	case PriorityHigh:
		return "high"
	case PriorityCritical:
		return "critical"
	case PriorityUrgent:
		return "urgent"
	default:
		return "unknown"
	}
}