package k8s

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"clawreef/internal/config"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
)

// ConnectionMode defines how to connect to Kubernetes
type ConnectionMode string

const (
	// ModeAuto automatically detects in-cluster or out-of-cluster
	ModeAuto ConnectionMode = "auto"
	// ModeInCluster forces in-cluster configuration
	ModeInCluster ConnectionMode = "incluster"
	// ModeOutOfCluster forces out-of-cluster configuration using kubeconfig
	ModeOutOfCluster ConnectionMode = "outofcluster"
)

// Client wraps the Kubernetes client
type Client struct {
	Clientset                kubernetes.Interface
	Config                   *rest.Config
	Namespace                string
	StorageClass             string
	StorageProfile           string
	HostPathFallbackEnabled  bool
	PVCBindTimeout           time.Duration
	ControlPlaneStorageClass string
	InstanceStorageClass     string
	WorkspaceStorageClass    string
	WorkspaceAccessMode      string
	HostPathPrefix           string
	WorkspaceRoot            string
	WorkspacePVCClaimName    string
	WorkspaceNFSServer       string
	WorkspaceNFSPath         string
	Mode                     ConnectionMode
}

var (
	// globalClient is the global K8s client instance
	globalClient        *Client
	k8sNameInvalidChars = regexp.MustCompile(`[^a-z0-9-]+`)
	k8sNameExtraDashes  = regexp.MustCompile(`-+`)
)

// Initialize initializes the Kubernetes client with support for both in-cluster and out-of-cluster modes
// Configuration priority:
// 1. configs/k8s.yaml - Main configuration file
// 2. Environment variables - Override config file settings
func Initialize(cfg *config.Config) error {
	mode := ConnectionMode(cfg.GetMode())
	if mode == "" {
		mode = ModeAuto
	}

	var restConfig *rest.Config
	var err error
	var detectedMode ConnectionMode

	switch mode {
	case ModeInCluster:
		restConfig, err = buildInClusterConfig(cfg)
		if err != nil {
			return fmt.Errorf("failed to initialize in-cluster config: %w", err)
		}
		detectedMode = ModeInCluster

	case ModeOutOfCluster:
		restConfig, err = buildOutOfClusterConfig(cfg)
		if err != nil {
			return fmt.Errorf("failed to initialize out-of-cluster config: %w", err)
		}
		detectedMode = ModeOutOfCluster

	case ModeAuto:
		// Try in-cluster first
		restConfig, err = buildInClusterConfig(cfg)
		if err == nil {
			detectedMode = ModeInCluster
		} else {
			// Fall back to out-of-cluster
			restConfig, err = buildOutOfClusterConfig(cfg)
			if err != nil {
				return fmt.Errorf("failed to initialize K8s client (both in-cluster and out-of-cluster failed): %w", err)
			}
			detectedMode = ModeOutOfCluster
		}

	default:
		return fmt.Errorf("invalid K8S_MODE: %s (must be 'auto', 'incluster', or 'outofcluster')", mode)
	}

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

	globalClient = &Client{
		Clientset:                clientset,
		Config:                   restConfig,
		Namespace:                cfg.GetNamespace(),
		StorageClass:             cfg.GetStorageClass(),
		StorageProfile:           cfg.GetStorageProfile(),
		HostPathFallbackEnabled:  cfg.Storage.HostPathFallbackEnabled,
		PVCBindTimeout:           cfg.GetPVCBindTimeout(),
		ControlPlaneStorageClass: cfg.GetControlPlaneStorageClass(),
		InstanceStorageClass:     cfg.GetInstanceStorageClass(),
		WorkspaceStorageClass:    cfg.GetWorkspaceStorageClass(),
		WorkspaceAccessMode:      cfg.GetWorkspaceAccessMode(),
		HostPathPrefix:           cfg.GetHostPathPrefix(),
		WorkspaceRoot:            cfg.Runtime.WorkspaceRoot,
		WorkspacePVCClaimName:    cfg.Runtime.WorkspacePVCClaimName,
		WorkspaceNFSServer:       cfg.Runtime.WorkspaceNFSServer,
		WorkspaceNFSPath:         cfg.Runtime.WorkspaceNFSPath,
		Mode:                     detectedMode,
	}

	return nil
}

// buildInClusterConfig builds REST config for in-cluster mode
func buildInClusterConfig(cfg *config.Config) (*rest.Config, error) {
	// Use standard in-cluster configuration
	restConfig, err := rest.InClusterConfig()
	if err != nil {
		return nil, err
	}

	// Apply timeout from config
	if cfg.Kubernetes.Common.Timeout > 0 {
		restConfig.Timeout = time.Duration(cfg.Kubernetes.Common.Timeout) * time.Second
	}

	return restConfig, nil
}

// buildOutOfClusterConfig builds REST config from kubeconfig file
func buildOutOfClusterConfig(cfg *config.Config) (*rest.Config, error) {
	kubeconfig := cfg.GetKubeconfigPath()

	// Priority for kubeconfig location:
	// 1. Config file setting (configs/k8s.yaml -> kubernetes.outOfCluster.kubeconfig)
	// 2. KUBECONFIG environment variable
	// 3. K8S_KUBECONFIG environment variable
	// 4. Default location (~/.kube/config)
	if kubeconfig == "" {
		if kubeconfig = os.Getenv("KUBECONFIG"); kubeconfig == "" {
			if kubeconfig = os.Getenv("K8S_KUBECONFIG"); kubeconfig == "" {
				home := homedir.HomeDir()
				kubeconfig = filepath.Join(home, ".kube", "config")
			}
		}
	}

	// Check if kubeconfig file exists
	if _, err := os.Stat(kubeconfig); os.IsNotExist(err) {
		return nil, fmt.Errorf("kubeconfig file not found at %s", kubeconfig)
	}

	// Build config from kubeconfig file
	loadingRules := &clientcmd.ClientConfigLoadingRules{
		ExplicitPath: kubeconfig,
	}

	configOverrides := &clientcmd.ConfigOverrides{}

	// Override context if specified
	if cfg.Kubernetes.OutOfCluster.Context != "" {
		configOverrides.CurrentContext = cfg.Kubernetes.OutOfCluster.Context
	}

	// Override API server if specified
	if cfg.Kubernetes.OutOfCluster.APIServer != "" {
		configOverrides.ClusterInfo.Server = cfg.Kubernetes.OutOfCluster.APIServer
	}

	kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
		loadingRules,
		configOverrides,
	)

	restConfig, err := kubeConfig.ClientConfig()
	if err != nil {
		return nil, fmt.Errorf("failed to build config from kubeconfig %s: %w", kubeconfig, err)
	}

	// Apply TLS settings
	if cfg.Kubernetes.OutOfCluster.TLS.InsecureSkipVerify {
		restConfig.Insecure = true
		restConfig.TLSClientConfig.Insecure = true
	}

	if cfg.Kubernetes.OutOfCluster.TLS.CAFile != "" {
		restConfig.TLSClientConfig.CAFile = cfg.Kubernetes.OutOfCluster.TLS.CAFile
	}

	if cfg.Kubernetes.OutOfCluster.TLS.CertFile != "" {
		restConfig.TLSClientConfig.CertFile = cfg.Kubernetes.OutOfCluster.TLS.CertFile
	}

	if cfg.Kubernetes.OutOfCluster.TLS.KeyFile != "" {
		restConfig.TLSClientConfig.KeyFile = cfg.Kubernetes.OutOfCluster.TLS.KeyFile
	}

	// Apply timeout from config
	if cfg.Kubernetes.Common.Timeout > 0 {
		restConfig.Timeout = time.Duration(cfg.Kubernetes.Common.Timeout) * time.Second
	}

	return restConfig, nil
}

// GetClient returns the global Kubernetes client
func GetClient() *Client {
	return globalClient
}

// IsInCluster returns true if running in-cluster
func (c *Client) IsInCluster() bool {
	return c.Mode == ModeInCluster
}

// IsOutOfCluster returns true if running out-of-cluster
func (c *Client) IsOutOfCluster() bool {
	return c.Mode == ModeOutOfCluster
}

// GetConnectionMode returns the current connection mode
func (c *Client) GetConnectionMode() ConnectionMode {
	return c.Mode
}

// GetNamespace returns the namespace for an instance
func (c *Client) GetNamespace(userID int) string {
	return sanitizeK8sName(fmt.Sprintf("%s-user-%d", c.Namespace, userID))
}

// GetSystemNamespace returns the control-plane namespace used by ClawManager itself.
func (c *Client) GetSystemNamespace() string {
	return sanitizeK8sName(fmt.Sprintf("%s-system", c.Namespace))
}

// GetPodName returns the pod name for an instance
func (c *Client) GetPodName(instanceID int, instanceName string) string {
	return sanitizeK8sName(fmt.Sprintf("clawreef-%d-%s", instanceID, instanceName))
}

// GetDeploymentName returns the deployment name for an instance.
func (c *Client) GetDeploymentName(instanceID int, instanceName string) string {
	return sanitizeK8sName(fmt.Sprintf("clawreef-%d-%s", instanceID, instanceName))
}

// GetPVCName returns the PVC name for an instance
func (c *Client) GetPVCName(instanceID int) string {
	return sanitizeK8sName(fmt.Sprintf("clawreef-%d-pvc", instanceID))
}

// GetTeamSharedPVCName returns the PVC name used for a Team shared workspace.
func (c *Client) GetTeamSharedPVCName(teamID int) string {
	return sanitizeK8sName(fmt.Sprintf("clawreef-team-%d-shared", teamID))
}

// GetTeamSecretName returns the Secret name used for Team Redis URL and token env.
func (c *Client) GetTeamSecretName(teamID int) string {
	return sanitizeK8sName(fmt.Sprintf("clawreef-team-%d-bus", teamID))
}

// GetTeamConfigMapName returns the ConfigMap name used for Team roster/config.
func (c *Client) GetTeamConfigMapName(teamID int) string {
	return sanitizeK8sName(fmt.Sprintf("clawreef-team-%d-config", teamID))
}

// GetServiceName returns the service name for an instance
func (c *Client) GetServiceName(instanceID int, instanceName string) string {
	return sanitizeK8sName(fmt.Sprintf("clawreef-%d-%s-svc", instanceID, instanceName))
}

// GetOpenClawBootstrapSecretName returns the secret name used to store rendered OpenClaw bootstrap payloads.
func (c *Client) GetOpenClawBootstrapSecretName(instanceID int, instanceName string) string {
	return sanitizeK8sName(fmt.Sprintf("clawreef-%d-%s-openclaw-bootstrap", instanceID, instanceName))
}

// GetNetworkPolicyName returns the default network policy name for an instance.
func (c *Client) GetNetworkPolicyName(instanceID int, instanceName string) string {
	return sanitizeK8sName(fmt.Sprintf("clawreef-%d-%s-netpol", instanceID, instanceName))
}

func sanitizeK8sName(name string) string {
	sanitized := strings.ToLower(name)
	sanitized = k8sNameInvalidChars.ReplaceAllString(sanitized, "-")
	sanitized = k8sNameExtraDashes.ReplaceAllString(sanitized, "-")
	sanitized = strings.Trim(sanitized, "-")

	if sanitized == "" {
		return "clawreef"
	}

	if len(sanitized) > 63 {
		sanitized = strings.Trim(sanitized[:63], "-")
		if sanitized == "" {
			return "clawreef"
		}
	}

	return sanitized
}
