package k8s

import (
	"context"
	"fmt"
	"os"
	"sync"
	"time"

	"github.com/rwadurian/mpc-system/services/session-coordinator/application/ports/output"
	"go.uber.org/zap"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

// PartyEndpoint represents a discovered party
// Note: Address removed - parties connect to Message Router themselves
type PartyEndpoint struct {
	PodName string
	Ready   bool
	Role    output.PartyRole // Party role extracted from pod labels
}

// PartyDiscovery handles Kubernetes-based party service discovery
type PartyDiscovery struct {
	clientset      *kubernetes.Clientset
	namespace      string
	serviceName    string
	labelSelector  string
	logger         *zap.Logger
	endpoints      []PartyEndpoint
	mu             sync.RWMutex
	refreshInterval time.Duration
}

// NewPartyDiscovery creates a new Kubernetes party discovery service
func NewPartyDiscovery(logger *zap.Logger) (*PartyDiscovery, error) {
	var config *rest.Config
	var err error

	// Try in-cluster config first (when running inside K8s)
	config, err = rest.InClusterConfig()
	if err != nil {
		// Fallback to kubeconfig for local development
		kubeconfig := os.Getenv("KUBECONFIG")
		if kubeconfig == "" {
			kubeconfig = os.Getenv("HOME") + "/.kube/config"
		}
		config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
		if err != nil {
			return nil, fmt.Errorf("failed to create k8s config: %w", err)
		}
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, fmt.Errorf("failed to create k8s clientset: %w", err)
	}

	namespace := os.Getenv("K8S_NAMESPACE")
	if namespace == "" {
		namespace = "default"
	}

	serviceName := os.Getenv("MPC_PARTY_SERVICE_NAME")
	if serviceName == "" {
		serviceName = "mpc-server-party"
	}

	labelSelector := os.Getenv("MPC_PARTY_LABEL_SELECTOR")
	if labelSelector == "" {
		labelSelector = "app=mpc-server-party"
	}

	refreshInterval := 30 * time.Second
	if interval := os.Getenv("MPC_PARTY_DISCOVERY_INTERVAL"); interval != "" {
		if d, err := time.ParseDuration(interval); err == nil {
			refreshInterval = d
		}
	}

	pd := &PartyDiscovery{
		clientset:       clientset,
		namespace:       namespace,
		serviceName:     serviceName,
		labelSelector:   labelSelector,
		logger:          logger,
		endpoints:       []PartyEndpoint{},
		refreshInterval: refreshInterval,
	}

	// Initial discovery
	if err := pd.refresh(); err != nil {
		logger.Warn("Initial party discovery failed, will retry", zap.Error(err))
	}

	// Start background refresh
	go pd.backgroundRefresh()

	return pd, nil
}

// GetAvailableParties returns a list of available party endpoints
// Implements output.PartyPoolPort interface
func (pd *PartyDiscovery) GetAvailableParties() []output.PartyEndpoint {
	pd.mu.RLock()
	defer pd.mu.RUnlock()

	// Return only ready endpoints
	available := make([]output.PartyEndpoint, 0, len(pd.endpoints))
	for _, ep := range pd.endpoints {
		if ep.Ready {
			available = append(available, output.PartyEndpoint{
				PartyID: ep.PodName, // Use pod name as party ID
				Ready:   ep.Ready,
				Role:    ep.Role,
			})
		}
	}
	return available
}

// GetAvailablePartiesByRole returns available parties filtered by role
// Implements output.PartyPoolPort interface
func (pd *PartyDiscovery) GetAvailablePartiesByRole(role output.PartyRole) []output.PartyEndpoint {
	pd.mu.RLock()
	defer pd.mu.RUnlock()

	// Return only ready endpoints matching the specified role
	available := make([]output.PartyEndpoint, 0)
	for _, ep := range pd.endpoints {
		if ep.Ready && ep.Role == role {
			available = append(available, output.PartyEndpoint{
				PartyID: ep.PodName,
				Ready:   ep.Ready,
				Role:    ep.Role,
			})
		}
	}
	return available
}

// SelectParties randomly selects n parties from the available pool
// Implements output.PartyPoolPort interface
func (pd *PartyDiscovery) SelectParties(n int) ([]output.PartyEndpoint, error) {
	available := pd.GetAvailableParties()

	if len(available) < n {
		return nil, fmt.Errorf("insufficient parties: need %d, have %d", n, len(available))
	}

	// For now, return first n parties
	// TODO: Implement random selection or load balancing strategy
	selected := make([]output.PartyEndpoint, n)
	copy(selected, available[:n])

	return selected, nil
}

// SelectPartiesWithFilter selects parties based on filter criteria
// Implements output.PartyPoolPort interface
func (pd *PartyDiscovery) SelectPartiesWithFilter(filter output.PartySelectionFilter) ([]output.PartyEndpoint, error) {
	var available []output.PartyEndpoint

	// If role is specified, filter by role; otherwise get all available parties
	if filter.Role != "" {
		available = pd.GetAvailablePartiesByRole(filter.Role)
	} else {
		available = pd.GetAvailableParties()
	}

	if len(available) < filter.Count {
		return nil, fmt.Errorf("insufficient parties with role %s: need %d, have %d", filter.Role, filter.Count, len(available))
	}

	// For now, return first n parties
	// TODO: Implement random selection or load balancing strategy
	selected := make([]output.PartyEndpoint, filter.Count)
	copy(selected, available[:filter.Count])

	return selected, nil
}

// refresh updates the list of party endpoints from Kubernetes
func (pd *PartyDiscovery) refresh() error {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// Get pods matching the label selector
	pods, err := pd.clientset.CoreV1().Pods(pd.namespace).List(ctx, metav1.ListOptions{
		LabelSelector: pd.labelSelector,
	})
	if err != nil {
		return fmt.Errorf("failed to list pods: %w", err)
	}

	endpoints := make([]PartyEndpoint, 0, len(pods.Items))
	for _, pod := range pods.Items {
		// Check if pod is ready
		ready := false
		for _, condition := range pod.Status.Conditions {
			if condition.Type == "Ready" && condition.Status == "True" {
				ready = true
				break
			}
		}

		// Extract party role from pod labels
		// Default to persistent if label not found
		role := output.PartyRolePersistent
		if roleLabel, ok := pod.Labels["party-role"]; ok {
			role = output.PartyRole(roleLabel)
		}

		// Add party to pool (no address needed - parties connect to Message Router)
		endpoints = append(endpoints, PartyEndpoint{
			PodName: pod.Name,
			Ready:   ready,
			Role:    role,
		})
	}

	pd.mu.Lock()
	pd.endpoints = endpoints
	pd.mu.Unlock()

	pd.logger.Info("Party endpoints refreshed",
		zap.Int("total", len(endpoints)),
		zap.Int("ready", pd.countReady(endpoints)))

	return nil
}

// backgroundRefresh periodically refreshes the party endpoints
func (pd *PartyDiscovery) backgroundRefresh() {
	ticker := time.NewTicker(pd.refreshInterval)
	defer ticker.Stop()

	for range ticker.C {
		if err := pd.refresh(); err != nil {
			pd.logger.Error("Failed to refresh party endpoints", zap.Error(err))
		}
	}
}

// countReady counts the number of ready endpoints
func (pd *PartyDiscovery) countReady(endpoints []PartyEndpoint) int {
	count := 0
	for _, ep := range endpoints {
		if ep.Ready {
			count++
		}
	}
	return count
}

// Close stops the background refresh
func (pd *PartyDiscovery) Close() {
	// Ticker will be stopped when the goroutine exits
	pd.logger.Info("Party discovery service closed")
}
