// Package transport - Multi-transport failover for war zones
package transport

import (
	"fmt"
	"net"
	"sync"
	"time"
)

// FailoverTransport implements automatic failover between multiple transports
// Critical for war zones: Tor → Mesh → Bluetooth → Direct
type FailoverTransport struct {
	transports     []Transport          // Ordered list of transports to try
	currentIndex   int                  // Currently active transport
	mu             sync.RWMutex
	healthCheck    map[TransportType]time.Time // Last successful connection
	retryInterval  time.Duration
	maxRetries     int
	onFailover     func(from, to TransportType) // Callback when failing over
}

// FailoverConfig holds failover configuration
type FailoverConfig struct {
	RetryInterval time.Duration // Time between retry attempts
	MaxRetries    int           // Max retries per transport before failing over
	HealthCheckInterval time.Duration // How often to health check transports
}

// NewFailoverTransport creates a new failover transport
func NewFailoverTransport(transports []Transport, config *FailoverConfig) *FailoverTransport {
	if config == nil {
		config = &FailoverConfig{
			RetryInterval:       5 * time.Second,
			MaxRetries:          3,
			HealthCheckInterval: 30 * time.Second,
		}
	}

	ft := &FailoverTransport{
		transports:    transports,
		currentIndex:  0,
		healthCheck:   make(map[TransportType]time.Time),
		retryInterval: config.RetryInterval,
		maxRetries:    config.MaxRetries,
	}

	// Start health checking
	go ft.healthCheckLoop(config.HealthCheckInterval)

	return ft
}

// Connect attempts to connect using transports in priority order
func (ft *FailoverTransport) Connect(address string) (net.Conn, error) {
	// Try each transport in order
	for attempt := 0; attempt < len(ft.transports)*ft.maxRetries; attempt++ {
		ft.mu.Lock()
		index := ft.currentIndex
		transport := ft.transports[index]
		ft.mu.Unlock()

		transportType := transport.Type()

		// Attempt connection
		conn, err := transport.Connect(address)
		if err == nil {
			// Success!
			ft.mu.Lock()
			ft.healthCheck[transportType] = time.Now()
			ft.mu.Unlock()

			fmt.Printf("✓ Connected via %s\n", transportType)
			return conn, nil
		}

		fmt.Printf("✗ %s connection failed: %v\n", transportType, err)

		// Move to next transport
		ft.mu.Lock()
		oldIndex := ft.currentIndex
		ft.currentIndex = (ft.currentIndex + 1) % len(ft.transports)
		newTransport := ft.transports[ft.currentIndex]
		ft.mu.Unlock()

		// Notify failover
		if ft.onFailover != nil && oldIndex != ft.currentIndex {
			go ft.onFailover(transport.Type(), newTransport.Type())
		}

		// Wait before retry
		time.Sleep(ft.retryInterval)
	}

	return nil, fmt.Errorf("all transports failed after %d attempts", len(ft.transports)*ft.maxRetries)
}

// Listen starts listening on the highest priority available transport
func (ft *FailoverTransport) Listen(address string) (net.Listener, error) {
	ft.mu.RLock()
	defer ft.mu.RUnlock()

	// Try each transport
	for _, transport := range ft.transports {
		listener, err := transport.Listen(address)
		if err == nil {
			fmt.Printf("✓ Listening on %s\n", transport.Type())
			return listener, nil
		}
		fmt.Printf("✗ Listen failed on %s: %v\n", transport.Type(), err)
	}

	return nil, fmt.Errorf("all transports failed to listen")
}

// Type returns the currently active transport type
func (ft *FailoverTransport) Type() TransportType {
	ft.mu.RLock()
	defer ft.mu.RUnlock()

	if ft.currentIndex < len(ft.transports) {
		return ft.transports[ft.currentIndex].Type()
	}
	return TransportType(-1)
}

// Close closes all transports
func (ft *FailoverTransport) Close() error {
	ft.mu.Lock()
	defer ft.mu.Unlock()

	var lastErr error
	for _, transport := range ft.transports {
		if err := transport.Close(); err != nil {
			lastErr = err
		}
	}
	return lastErr
}

// GetCurrentTransport returns the currently active transport
func (ft *FailoverTransport) GetCurrentTransport() Transport {
	ft.mu.RLock()
	defer ft.mu.RUnlock()

	if ft.currentIndex < len(ft.transports) {
		return ft.transports[ft.currentIndex]
	}
	return nil
}

// GetTransports returns all available transports
func (ft *FailoverTransport) GetTransports() []Transport {
	ft.mu.RLock()
	defer ft.mu.RUnlock()

	return ft.transports
}

// SetTransportPriority reorders transports by priority
func (ft *FailoverTransport) SetTransportPriority(types []TransportType) error {
	ft.mu.Lock()
	defer ft.mu.Unlock()

	if len(types) != len(ft.transports) {
		return fmt.Errorf("priority list length mismatch")
	}

	// Create new ordered list
	newTransports := make([]Transport, 0, len(ft.transports))
	for _, targetType := range types {
		found := false
		for _, transport := range ft.transports {
			if transport.Type() == targetType {
				newTransports = append(newTransports, transport)
				found = true
				break
			}
		}
		if !found {
			return fmt.Errorf("transport type %s not found", targetType)
		}
	}

	ft.transports = newTransports
	ft.currentIndex = 0

	return nil
}

// healthCheckLoop periodically checks transport health
func (ft *FailoverTransport) healthCheckLoop(interval time.Duration) {
	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	for range ticker.C {
		ft.performHealthCheck()
	}
}

// performHealthCheck checks if current transport is still healthy
func (ft *FailoverTransport) performHealthCheck() {
	ft.mu.RLock()
	current := ft.transports[ft.currentIndex]
	lastSuccess, exists := ft.healthCheck[current.Type()]
	ft.mu.RUnlock()

	// If no successful connection in last 5 minutes, try next transport
	if !exists || time.Since(lastSuccess) > 5*time.Minute {
		ft.mu.Lock()
		oldIndex := ft.currentIndex
		ft.currentIndex = (ft.currentIndex + 1) % len(ft.transports)
		ft.mu.Unlock()

		if ft.onFailover != nil && oldIndex != ft.currentIndex {
			ft.mu.RLock()
			go ft.onFailover(ft.transports[oldIndex].Type(), ft.transports[ft.currentIndex].Type())
			ft.mu.RUnlock()
		}
	}
}

// SetOnFailover sets callback for failover events
func (ft *FailoverTransport) SetOnFailover(callback func(from, to TransportType)) {
	ft.mu.Lock()
	defer ft.mu.Unlock()
	ft.onFailover = callback
}

// GetHealthStatus returns health status of all transports
func (ft *FailoverTransport) GetHealthStatus() map[TransportType]time.Time {
	ft.mu.RLock()
	defer ft.mu.RUnlock()

	status := make(map[TransportType]time.Time)
	for k, v := range ft.healthCheck {
		status[k] = v
	}
	return status
}

// String returns a string representation
func (ft *FailoverTransport) String() string {
	ft.mu.RLock()
	defer ft.mu.RUnlock()

	currentType := "none"
	if ft.currentIndex < len(ft.transports) {
		currentType = ft.transports[ft.currentIndex].Type().String()
	}

	return fmt.Sprintf("FailoverTransport(current=%s, transports=%d)",
		currentType, len(ft.transports))
}

// ConnectWithRetry attempts connection with exponential backoff
func (ft *FailoverTransport) ConnectWithRetry(address string, maxAttempts int) (net.Conn, error) {
	var conn net.Conn
	var err error

	backoff := 1 * time.Second

	for attempt := 1; attempt <= maxAttempts; attempt++ {
		conn, err = ft.Connect(address)
		if err == nil {
			return conn, nil
		}

		fmt.Printf("Attempt %d/%d failed, retrying in %v...\n", attempt, maxAttempts, backoff)
		time.Sleep(backoff)

		// Exponential backoff with jitter
		backoff *= 2
		if backoff > 60*time.Second {
			backoff = 60 * time.Second
		}
	}

	return nil, fmt.Errorf("failed after %d attempts: %w", maxAttempts, err)
}
