const k8s = require('@kubernetes/client-node');
const config = require('../config');

class KubernetesClient {
  constructor() {
    this.kubeConfig = new k8s.KubeConfig();
    
    // Try loading from default location (works in-cluster)
    try {
      this.kubeConfig.loadFromDefault();
    } catch (err) {
      console.warn('Could not load kubeconfig from default location, using incluster config');
      this.kubeConfig.loadFromCluster();
    }
    
    this.customObjectsApi = this.kubeConfig.makeApiClient(k8s.CustomObjectsApi);
    
    this.group = 'container.mom';
    this.version = 'v1alpha1';
    this.namespace = process.env.NAMESPACE || 'container-mom-system';  // Updated to match actual namespace
    this.plural = 'containermomdeployments';
    this.templatesPlural = 'templates';
    
    console.log(`Using namespace ${this.namespace} for ContainerMomDeployments`);
  }

  // List all deployments for a user
  async listDeployments(userId) {
    try {
      console.log(`Listing deployments for user: ${userId} in namespace: ${this.namespace}`);
      
      // Handle wildcard user ID separately - don't use as label selector
      let labelSelector = undefined;
      if (userId !== '*') {
        labelSelector = `createdBy=${userId}`;
        console.log(`Using label selector: ${labelSelector}`);
      } else {
        console.log('Using wildcard userId - no label selector applied');
      }
      
      let deployments = [];
      
      try {
        // First attempt - use the configured namespace
        const response = await this.customObjectsApi.listNamespacedCustomObject(
          this.group,
          this.version,
          this.namespace,
          this.plural,
          undefined, // pretty
          undefined, // allowWatchBookmarks
          undefined, // _continue
          undefined, // fieldSelector 
          labelSelector // labelSelector - only apply if not wildcard
        );
        
        deployments = response.body.items;
        console.log(`Found ${deployments.length} deployments in namespace ${this.namespace}`);
      } catch (error) {
        console.error(`Error listing deployments in namespace ${this.namespace}:`, error);
        // Continue with empty deployments array
      }
      
      // If we're using wildcard and no deployments were found, try other namespaces
      const fallbackNamespaces = ['container-mom-system', 'container-mom-system-test'];
      
      if (userId === '*' && deployments.length === 0) {
        console.log('No deployments found in primary namespace, trying fallback namespaces');
        
        for (const namespace of fallbackNamespaces) {
          if (namespace === this.namespace) continue; // Skip if same as primary
          
          try {
            console.log(`Trying fallback namespace: ${namespace}`);
            const fallbackResponse = await this.customObjectsApi.listNamespacedCustomObject(
              this.group,
              this.version,
              namespace,
              this.plural,
              undefined, // pretty
              undefined, // allowWatchBookmarks
              undefined, // _continue
              undefined, // fieldSelector 
              labelSelector // labelSelector - only apply if not wildcard
            );
            
            const fallbackDeployments = fallbackResponse.body.items;
            console.log(`Found ${fallbackDeployments.length} deployments in namespace ${namespace}`);
            
            // Append to main deployments array
            deployments = [...deployments, ...fallbackDeployments];
          } catch (fallbackError) {
            console.error(`Error listing deployments in fallback namespace ${namespace}:`, fallbackError);
          }
        }
      }
      
      console.log(`Total deployments found across all namespaces: ${deployments.length}`);
      return deployments.map(this._formatDeployment);
    } catch (error) {
      console.error('Error listing deployments:', error);
      throw new Error('Failed to fetch deployments from Kubernetes API');
    }
  }

  // Get a specific deployment
  async getDeployment(name, userId) {
    try {
      console.log(`Getting deployment: ${name} for user: ${userId}`);
      const response = await this.customObjectsApi.getNamespacedCustomObject(
        this.group,
        this.version,
        this.namespace,
        this.plural,
        name
      );
      
      const deployment = response.body;
      // Skip ownership check if using wildcard
      if (userId !== '*' && deployment.metadata.labels?.createdBy && deployment.metadata.labels.createdBy !== userId) {
        throw new Error('Deployment not found or not owned by user');
      }
      
      return this._formatDeployment(deployment);
    } catch (error) {
      console.error('Error getting deployment:', error);
      throw new Error('Failed to get deployment from Kubernetes API');
    }
  }

  // Create a new deployment
  async createDeployment(deploymentData, userId) {
    try {
      console.log(`Creating deployment for user: ${userId}`);
      // Convert from MongoDB model structure to ContainerMomDeployment CRD structure
      const k8sDeployment = this._createK8sDeployment(deploymentData, userId);
      
      const response = await this.customObjectsApi.createNamespacedCustomObject(
        this.group,
        this.version,
        this.namespace,
        this.plural,
        k8sDeployment
      );
      
      return this._formatDeployment(response.body);
    } catch (error) {
      console.error('Error creating deployment:', error);
      throw new Error(`Failed to create deployment: ${error.message}`);
    }
  }

  // Update a deployment
  async updateDeployment(name, deploymentData, userId) {
    try {
      console.log(`Updating deployment: ${name} for user: ${userId}`);
      // First check if the deployment exists and belongs to the user
      await this.getDeployment(name, userId);
      
      // Convert from MongoDB model structure to ContainerMomDeployment CRD structure
      const k8sDeployment = this._createK8sDeployment(deploymentData, userId);
      
      const response = await this.customObjectsApi.patchNamespacedCustomObject(
        this.group,
        this.version,
        this.namespace,
        this.plural,
        name,
        k8sDeployment,
        undefined, // pretty
        undefined, // dryRun
        undefined, // fieldManager
        undefined, // force
        { 
          headers: { 'Content-Type': 'application/merge-patch+json' } 
        }
      );
      
      return this._formatDeployment(response.body);
    } catch (error) {
      console.error('Error updating deployment:', error);
      throw new Error(`Failed to update deployment: ${error.message}`);
    }
  }

  // Delete a deployment
  async deleteDeployment(name, userId) {
    try {
      console.log(`Deleting deployment: ${name} for user: ${userId}`);
      // First check if the deployment exists and belongs to the user
      await this.getDeployment(name, userId);
      
      await this.customObjectsApi.deleteNamespacedCustomObject(
        this.group,
        this.version,
        this.namespace,
        this.plural,
        name
      );
      
      return { message: 'Deployment deleted successfully' };
    } catch (error) {
      console.error('Error deleting deployment:', error);
      throw new Error(`Failed to delete deployment: ${error.message}`);
    }
  }

  // List all available templates
  async listTemplates() {
    try {
      console.log('Listing available templates from CRDs');
      const response = await this.customObjectsApi.listNamespacedCustomObject(
        this.group,
        this.version,
        this.namespace,
        this.templatesPlural
      );
      
      return response.body.items.map(this._formatTemplate);
    } catch (error) {
      console.error('Error listing templates:', error);
      throw new Error('Failed to fetch templates from Kubernetes API');
    }
  }

  // Get a specific template by name
  async getTemplate(name) {
    try {
      console.log(`Getting template: ${name}`);
      const response = await this.customObjectsApi.getNamespacedCustomObject(
        this.group,
        this.version,
        this.namespace,
        this.templatesPlural,
        name
      );
      
      return this._formatTemplate(response.body);
    } catch (error) {
      console.error('Error getting template:', error);
      if (error.response && error.response.statusCode === 404) {
        return null;
      }
      throw new Error(`Failed to get template: ${error.message}`);
    }
  }

  // Create a deployment from a template
  async createDeploymentFromTemplate(templateName, deploymentName, clusters, subdomain, params) {
    try {
      console.log(`Creating deployment from template: ${templateName} with params:`, params);
      
      // Get the template
      const template = await this.getTemplate(templateName);
      if (!template) {
        throw new Error(`Template ${templateName} not found`);
      }
      
      // Find primary component (deployment type)
      const primaryComponent = template.components.find(c => c.type === 'deployment');
      if (!primaryComponent) {
        throw new Error('No deployment component found in template');
      }
      
      // Create the deployment object
      const deploymentData = {
        name: deploymentName,
        sourceType: 'container',
        containerImage: primaryComponent.properties.image,
        regions: clusters,
        domain: template.networkConfig?.domain?.domainName || '',
        subdomain: subdomain,
        useWildcard: template.networkConfig?.domain?.useWildcard || false,
        env: [],
        resources: primaryComponent.properties.resources || { cpu: '500m', memory: '512Mi' }
      };
      
      // Process all environment variables from the template
      if (primaryComponent.properties.env && primaryComponent.properties.env.length > 0) {
        // Start with template's environment variables
        deploymentData.env = [...primaryComponent.properties.env];
        
        // Replace parameter placeholders in environment variables
        deploymentData.env = deploymentData.env.map(env => {
          const value = env.value.replace(/\$\{([^}]+)\}/g, (match, paramName) => {
            return params[paramName] !== undefined ? params[paramName] : match;
          });
          return { key: env.key, value };
        });
      }

      // Check for auto-generated parameters that need to be added as env vars
      if (template.parameters) {
        template.parameters.forEach(param => {
          // Skip parameters that were already processed in env vars
          const alreadyInEnv = deploymentData.env.some(env => 
            env.value.includes(`\${${param.name}}`) || env.key === param.name
          );
          
          if (!alreadyInEnv && params[param.name]) {
            // Add as an environment variable so it's available in the container
            deploymentData.env.push({
              key: param.name,
              value: params[param.name]
            });
          }
        });
      }
      
      // Add metadata about the template
      deploymentData.template = {
        name: templateName,
        id: template.id
      };
      
      console.log('Processed template deployment data:', deploymentData);
      
      // Create the deployment using the template values - use "system" instead of wildcard "*"
      return await this.createDeployment(deploymentData, 'system-created'); // Using valid label value instead of wildcard
    } catch (error) {
      console.error('Error creating deployment from template:', error);
      throw new Error(`Failed to create deployment from template: ${error.message}`);
    }
  }

  // Helper to convert from MongoDB model to K8s CRD
  _createK8sDeployment(data, userId) {
    const { name, sourceType, gitUrl, containerImage, regions, domain, subdomain, useWildcard, env, resources } = data;
    
    // Generate a unique name that is valid for Kubernetes (lowercase, no spaces, etc)
    const k8sName = name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
    
    // Sanitize userId for use as a Kubernetes label
    // Label values must match regex: (([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?
    const sanitizedUserId = userId === '*' ? 'system-created' : 
      userId.replace(/[^a-zA-Z0-9\-_\.]/g, '-');
    
    return {
      apiVersion: `${this.group}/${this.version}`,
      kind: 'ContainerMomDeployment',
      metadata: {
        name: k8sName,
        labels: {
          createdBy: sanitizedUserId
        }
      },
      spec: {
        source: sourceType === 'git' 
          ? { 
              gitUrl: gitUrl,
              // Optional build config could be added here
            }
          : { 
              image: containerImage 
            },
        clusters: regions,
        domain: {
          domainName: domain,
          prefix: subdomain,
          useWildcard: useWildcard || false,
          tls: {
            autoCert: true
          }
        },
        resources: {
          cpu: `${resources.cpu || 0.5}`,
          memory: resources.memory || '512Mi'
        },
        env: env?.map(e => ({ name: e.key, value: e.value })) || []
      }
    };
  }

  // Helper to convert from K8s CRD to MongoDB-like format for API compatibility
  _formatDeployment(k8sDeployment) {
    const { metadata, spec, status } = k8sDeployment;
    
    // Safely access nested properties
    const labels = metadata?.labels || {};
    const source = spec?.source || {};
    const domain = spec?.domain || {};
    const resources = spec?.resources || { cpu: '0.5', memory: '512Mi' };
    
    return {
      _id: metadata.name,
      name: metadata.name,
      sourceType: source.gitUrl ? 'git' : 'container',
      gitUrl: source.gitUrl || '',
      containerImage: source.image || '',
      regions: spec.clusters || [],
      domain: domain.domainName || '',
      subdomain: domain.prefix || '',
      useWildcard: domain.useWildcard || false,
      env: spec.env?.map(e => ({ key: e.name, value: e.value })) || [],
      resources: {
        cpu: parseFloat(resources.cpu || '0.5'),
        memory: resources.memory || '512Mi'
      },
      status: status?.phase || 'pending',
      createdBy: labels.createdBy || 'unknown',
      createdAt: metadata.creationTimestamp || new Date().toISOString(),
      error: status?.conditions?.find(c => c.type === 'Error')?.message || null
    };
  }

  // Helper to format template from K8s CRD for frontend use
  _formatTemplate(template) {
    const { metadata, spec } = template;
    
    // Extract components
    const components = spec.components?.map(component => {
      // Format properties based on component type
      let properties = {};
      
      if (component.properties) {
        properties = {
          ...component.properties,
          // Convert env array format if needed
          env: component.properties.env?.map(e => ({
            key: e.name || e.key,
            value: e.value
          })),
        };
      }
      
      return {
        name: component.name,
        type: component.type,
        properties
      };
    }) || [];
    
    // Format parameters
    const parameters = spec.parameters?.map(param => ({
      name: param.name,
      displayName: param.displayName,
      description: param.description,
      required: param.required,
      defaultValue: param.defaultValue,
      generate: param.generate
    })) || [];
    
    return {
      id: metadata.name,
      name: metadata.name,
      description: spec.description,
      components,
      parameters,
      networkConfig: spec.networkConfig
    };
  }
}

module.exports = new KubernetesClient(); 