/**
 * Kubernetes Adapter
 * Implements the InfrastructureAdapter interface for Kubernetes
 */

import { InfrastructureAdapter, NormalizedResource, ResourceSpec, NormalizedMetrics, TimeRange, HealthStatus, NormalizedRelationship } from '../types.js'
import { ResourceProvider } from '../../types/resource.js'
import { logger } from '../../lib/logger'
import * as k8s from '@kubernetes/client-node'

export class KubernetesAdapter implements InfrastructureAdapter {
  readonly provider: ResourceProvider = 'KUBERNETES'
  
  private k8sApi: k8s.CoreV1Api
  private k8sAppsApi: k8s.AppsV1Api
  private prometheusUrl?: string

  constructor(config: { kubeconfig?: string; context?: string; prometheusUrl?: string }) {
    const kc = new k8s.KubeConfig()
    
    if (config.kubeconfig) {
      kc.loadFromString(config.kubeconfig)
    } else if (process.env.KUBECONFIG) {
      kc.loadFromFile(process.env.KUBECONFIG)
    } else {
      kc.loadFromDefault()
    }

    if (config.context) {
      kc.setCurrentContext(config.context)
    }

    this.k8sApi = kc.makeApiClient(k8s.CoreV1Api)
    this.k8sAppsApi = kc.makeApiClient(k8s.AppsV1Api)
    this.prometheusUrl = config.prometheusUrl || process.env.PROMETHEUS_URL
  }

  async discoverResources(): Promise<NormalizedResource[]> {
    const resources: NormalizedResource[] = []
    
    try {
      // Discover pods
      const podsResponse = await this.k8sApi.listPodForAllNamespaces()
      if (podsResponse.body.items) {
        for (const pod of podsResponse.body.items) {
          resources.push(this.normalizePod(pod))
        }
      }

      // Discover services
      const servicesResponse = await this.k8sApi.listServiceForAllNamespaces()
      if (servicesResponse.body.items) {
        for (const service of servicesResponse.body.items) {
          resources.push(this.normalizeService(service))
        }
      }

      // Discover deployments
      const deploymentsResponse = await this.k8sAppsApi.listDeploymentForAllNamespaces()
      if (deploymentsResponse.body.items) {
        for (const deployment of deploymentsResponse.body.items) {
          resources.push(this.normalizeDeployment(deployment))
        }
      }
    } catch (error) {
      logger.error('Error discovering Kubernetes resources', { error })
      throw error
    }

    return resources
  }

  private normalizePod(pod: k8s.V1Pod): NormalizedResource {
    const namespace = pod.metadata?.namespace || 'default'
    const name = pod.metadata?.name || ''
    const uid = pod.metadata?.uid || ''

    return {
      id: `k8s-pod-${namespace}-${name}`,
      name: name,
      type: 'pod',
      provider: 'KUBERNETES',
      providerId: `${namespace}/${name}`,
      providerResourceId: `k8s://${namespace}/pods/${name}`,
      status: pod.status?.phase?.toLowerCase() || 'unknown',
      metadata: {
        namespace,
        uid,
        nodeName: pod.spec?.nodeName,
        containers: pod.spec?.containers?.map(c => ({ name: c.name, image: c.image })) || [],
        labels: pod.metadata?.labels || {},
      },
      tags: Object.keys(pod.metadata?.labels || {}),
      createdAt: pod.metadata?.creationTimestamp ? new Date(pod.metadata.creationTimestamp) : new Date(),
      updatedAt: new Date(),
    }
  }

  private normalizeService(service: k8s.V1Service): NormalizedResource {
    const namespace = service.metadata?.namespace || 'default'
    const name = service.metadata?.name || ''

    return {
      id: `k8s-svc-${namespace}-${name}`,
      name: name,
      type: 'service',
      provider: 'KUBERNETES',
      providerId: `${namespace}/${name}`,
      providerResourceId: `k8s://${namespace}/services/${name}`,
      status: 'active',
      metadata: {
        namespace,
        type: service.spec?.type,
        ports: service.spec?.ports?.map(p => ({ port: p.port, protocol: p.protocol })) || [],
        labels: service.metadata?.labels || {},
      },
      tags: Object.keys(service.metadata?.labels || {}),
      createdAt: service.metadata?.creationTimestamp ? new Date(service.metadata.creationTimestamp) : new Date(),
      updatedAt: new Date(),
    }
  }

  private normalizeDeployment(deployment: k8s.V1Deployment): NormalizedResource {
    const namespace = deployment.metadata?.namespace || 'default'
    const name = deployment.metadata?.name || ''

    return {
      id: `k8s-deploy-${namespace}-${name}`,
      name: name,
      type: 'deployment',
      provider: 'KUBERNETES',
      providerId: `${namespace}/${name}`,
      providerResourceId: `k8s://${namespace}/deployments/${name}`,
      status: deployment.status?.replicas === deployment.status?.readyReplicas ? 'running' : 'pending',
      metadata: {
        namespace,
        replicas: deployment.spec?.replicas || 0,
        readyReplicas: deployment.status?.readyReplicas || 0,
        labels: deployment.metadata?.labels || {},
      },
      tags: Object.keys(deployment.metadata?.labels || {}),
      createdAt: deployment.metadata?.creationTimestamp ? new Date(deployment.metadata.creationTimestamp) : new Date(),
      updatedAt: new Date(),
    }
  }

  async getResource(providerId: string): Promise<NormalizedResource | null> {
    try {
      const [namespace, name] = providerId.split('/')
      if (!namespace || !name) {
        return null
      }

      // Try to get as pod first
      try {
        const pod = await this.k8sApi.readNamespacedPod(name, namespace)
        if (pod.body) {
          return this.normalizePod(pod.body)
        }
      } catch (error) {
        // Not a pod, try service
        try {
          const service = await this.k8sApi.readNamespacedService(name, namespace)
          if (service.body) {
            return this.normalizeService(service.body)
          }
        } catch (error) {
          // Not a service, try deployment
          try {
            const deployment = await this.k8sAppsApi.readNamespacedDeployment(name, namespace)
            if (deployment.body) {
              return this.normalizeDeployment(deployment.body)
            }
          } catch (error) {
            return null
          }
        }
      }

      return null
    } catch (error) {
      logger.error(`Error getting Kubernetes resource ${providerId}`, { error, providerId })
      return null
    }
  }

  async createResource(spec: ResourceSpec): Promise<NormalizedResource> {
    try {
      if (spec.type === 'deployment') {
        const deployment: k8s.V1Deployment = {
          apiVersion: 'apps/v1',
          kind: 'Deployment',
          metadata: {
            name: spec.name,
            namespace: spec.config.namespace || 'default',
            labels: spec.tags?.reduce((acc, tag) => {
              const [key, value] = tag.split('=')
              acc[key] = value || 'true'
              return acc
            }, {} as Record<string, string>) || {},
          },
          spec: {
            replicas: spec.config.replicas || 1,
            selector: {
              matchLabels: {
                app: spec.name,
              },
            },
            template: {
              metadata: {
                labels: {
                  app: spec.name,
                },
              },
              spec: {
                containers: [
                  {
                    name: spec.name,
                    image: spec.config.image || 'nginx:latest',
                    resources: {
                      requests: {
                        cpu: spec.config.cpu || '100m',
                        memory: spec.config.memory || '128Mi',
                      },
                    },
                  },
                ],
              },
            },
          },
        }

        const result = await this.k8sAppsApi.createNamespacedDeployment(
          deployment.metadata!.namespace!,
          deployment
        )

        if (result.body.metadata) {
          return this.normalizeDeployment(result.body)
        }
      }

      throw new Error(`Unsupported resource type: ${spec.type}`)
    } catch (error) {
      logger.error('Error creating Kubernetes resource', { error })
      throw error
    }
  }

  async updateResource(providerId: string, spec: Partial<ResourceSpec>): Promise<NormalizedResource> {
    try {
      const [namespace, name] = providerId.split('/')
      if (!namespace || !name) {
        throw new Error('Invalid provider ID format')
      }

      // Try to update deployment
      try {
        const deployment = await this.k8sAppsApi.readNamespacedDeployment(name, namespace)
        if (deployment.body.spec) {
          if (spec.config?.replicas !== undefined) {
            deployment.body.spec.replicas = spec.config.replicas
          }
          const updated = await this.k8sAppsApi.replaceNamespacedDeployment(
            name,
            namespace,
            deployment.body
          )
          return this.normalizeDeployment(updated.body)
        }
      } catch (error) {
        // Not a deployment, continue
      }

      throw new Error('Resource not found or cannot be updated')
    } catch (error) {
      logger.error(`Error updating Kubernetes resource ${providerId}`, { error, providerId })
      throw error
    }
  }

  async deleteResource(providerId: string): Promise<boolean> {
    try {
      const [namespace, name] = providerId.split('/')
      if (!namespace || !name) {
        return false
      }

      // Try to delete deployment
      try {
        await this.k8sAppsApi.deleteNamespacedDeployment(name, namespace)
        return true
      } catch (error) {
        // Not a deployment, try pod
        try {
          await this.k8sApi.deleteNamespacedPod(name, namespace)
          return true
        } catch (error) {
          return false
        }
      }
    } catch (error) {
      logger.error(`Error deleting Kubernetes resource ${providerId}`, { error, providerId })
      return false
    }
  }

  async getMetrics(providerId: string, timeRange: TimeRange): Promise<NormalizedMetrics[]> {
    const metrics: NormalizedMetrics[] = []
    
    try {
      const [namespace, name] = providerId.split('/')
      if (!namespace || !name) {
        return []
      }

      // Try to get resource to determine type
      const resource = await this.getResource(providerId)
      if (!resource) {
        return []
      }

      // If Prometheus is configured, query it
      if (this.prometheusUrl) {
        const startTime = Math.floor(timeRange.start.getTime() / 1000)
        const endTime = Math.floor(timeRange.end.getTime() / 1000)

        if (resource.type === 'pod') {
          // CPU usage
          try {
            const cpuQuery = `rate(container_cpu_usage_seconds_total{pod="${name}",namespace="${namespace}"}[5m])`
            const cpuResponse = await fetch(
              `${this.prometheusUrl}/api/v1/query_range?query=${encodeURIComponent(cpuQuery)}&start=${startTime}&end=${endTime}&step=15s`
            )
            const cpuData = await cpuResponse.json()
            if (cpuData.status === 'success' && cpuData.data?.result?.[0]?.values) {
              for (const [timestamp, value] of cpuData.data.result[0].values) {
                metrics.push({
                  resourceId: providerId,
                  metricType: 'CPU_USAGE',
                  value: parseFloat(value),
                  timestamp: new Date(parseInt(timestamp) * 1000),
                  labels: { namespace, pod: name },
                })
              }
            }
          } catch (error) {
            logger.warn(`Failed to get CPU metrics for ${providerId}`, { error, providerId })
          }

          // Memory usage
          try {
            const memoryQuery = `container_memory_usage_bytes{pod="${name}",namespace="${namespace}"}`
            const memoryResponse = await fetch(
              `${this.prometheusUrl}/api/v1/query_range?query=${encodeURIComponent(memoryQuery)}&start=${startTime}&end=${endTime}&step=15s`
            )
            const memoryData = await memoryResponse.json()
            if (memoryData.status === 'success' && memoryData.data?.result?.[0]?.values) {
              for (const [timestamp, value] of memoryData.data.result[0].values) {
                metrics.push({
                  resourceId: providerId,
                  metricType: 'MEMORY_USAGE',
                  value: parseFloat(value),
                  timestamp: new Date(parseInt(timestamp) * 1000),
                  labels: { namespace, pod: name },
                })
              }
            }
          } catch (error) {
            logger.warn(`Failed to get memory metrics for ${providerId}`, { error, providerId })
          }
        } else if (resource.type === 'service') {
          // Network throughput for services
          try {
            const networkQuery = `rate(istio_requests_total{destination_service="${name}.${namespace}.svc.cluster.local"}[5m])`
            const networkResponse = await fetch(
              `${this.prometheusUrl}/api/v1/query_range?query=${encodeURIComponent(networkQuery)}&start=${startTime}&end=${endTime}&step=15s`
            )
            const networkData = await networkResponse.json()
            if (networkData.status === 'success' && networkData.data?.result?.[0]?.values) {
              for (const [timestamp, value] of networkData.data.result[0].values) {
                metrics.push({
                  resourceId: providerId,
                  metricType: 'NETWORK_THROUGHPUT',
                  value: parseFloat(value),
                  timestamp: new Date(parseInt(timestamp) * 1000),
                  labels: { namespace, service: name },
                })
              }
            }
          } catch (error) {
            logger.warn(`Failed to get network metrics for ${providerId}`, { error, providerId })
          }
        }
      } else {
        // Fallback to Metrics Server API if available
        try {
          const kc = new k8s.KubeConfig()
          kc.loadFromDefault()
          const metricsApi = kc.makeApiClient(k8s.MetricsV1beta1Api)

          if (resource.type === 'pod') {
            try {
              const podMetrics = await metricsApi.readNamespacedPodMetrics(name, namespace)
              if (podMetrics.body.containers) {
                for (const container of podMetrics.body.containers) {
                  if (container.usage?.cpu) {
                    const cpuValue = this.parseCpuValue(container.usage.cpu)
                    metrics.push({
                      resourceId: providerId,
                      metricType: 'CPU_USAGE',
                      value: cpuValue,
                      timestamp: new Date(),
                      labels: { container: container.name },
                    })
                  }
                  if (container.usage?.memory) {
                    const memoryValue = this.parseMemoryValue(container.usage.memory)
                    metrics.push({
                      resourceId: providerId,
                      metricType: 'MEMORY_USAGE',
                      value: memoryValue,
                      timestamp: new Date(),
                      labels: { container: container.name },
                    })
                  }
                }
              }
            } catch (error) {
              // Metrics Server not available
            }
          }
        } catch (error) {
          // Metrics API not available
        }
      }
    } catch (error) {
      logger.error(`Error getting Kubernetes metrics for ${providerId}`, { error, providerId })
    }

    return metrics
  }

  private parseCpuValue(cpu: string): number {
    // Parse CPU values like "100m" or "1" to cores
    if (cpu.endsWith('m')) {
      return parseFloat(cpu) / 1000
    }
    return parseFloat(cpu)
  }

  private parseMemoryValue(memory: string): number {
    // Parse memory values like "128Mi" or "1Gi" to bytes
    const units: Record<string, number> = {
      'Ki': 1024,
      'Mi': 1024 * 1024,
      'Gi': 1024 * 1024 * 1024,
      'Ti': 1024 * 1024 * 1024 * 1024,
    }
    
    for (const [unit, multiplier] of Object.entries(units)) {
      if (memory.endsWith(unit)) {
        return parseFloat(memory) * multiplier
      }
    }
    
    return parseFloat(memory)
  }

  async getRelationships(providerId: string): Promise<NormalizedRelationship[]> {
    const relationships: NormalizedRelationship[] = []
    
    try {
      const [namespace, name] = providerId.split('/')
      if (!namespace || !name) {
        return []
      }

      // Try to get resource to determine type
      const resource = await this.getResource(providerId)
      if (!resource) {
        return []
      }

      if (resource.type === 'pod') {
        // Find services that select this pod
        const servicesResponse = await this.k8sApi.listNamespacedService(namespace)
        if (servicesResponse.body.items) {
          for (const service of servicesResponse.body.items) {
            const selector = service.spec?.selector || {}
            const podLabels = (resource.metadata?.labels as Record<string, string>) || {}
            
            // Check if service selector matches pod labels
            const matches = Object.keys(selector).every(key => podLabels[key] === selector[key])
            if (matches) {
              relationships.push({
                sourceId: providerId,
                targetId: `${namespace}/${service.metadata?.name}`,
                type: 'exposed_by',
                metadata: {
                  serviceName: service.metadata?.name,
                  ports: service.spec?.ports?.map(p => p.port) || [],
                },
              })
            }
          }
        }

        // Find deployment that owns this pod
        const deploymentsResponse = await this.k8sAppsApi.listNamespacedDeployment(namespace)
        if (deploymentsResponse.body.items) {
          for (const deployment of deploymentsResponse.body.items) {
            const selector = deployment.spec?.selector?.matchLabels || {}
            const podLabels = (resource.metadata?.labels as Record<string, string>) || {}
            
            const matches = Object.keys(selector).every(key => podLabels[key] === selector[key])
            if (matches) {
              relationships.push({
                sourceId: `${namespace}/${deployment.metadata?.name}`,
                targetId: providerId,
                type: 'manages',
                metadata: {
                  deploymentName: deployment.metadata?.name,
                },
              })
            }
          }
        }
      } else if (resource.type === 'service') {
        // Find pods that this service selects
        const selector = (resource.metadata as any)?.selector || {}
        const podsResponse = await this.k8sApi.listNamespacedPod(namespace)
        if (podsResponse.body.items) {
          for (const pod of podsResponse.body.items) {
            const podLabels = pod.metadata?.labels || {}
            const matches = Object.keys(selector).every(key => podLabels[key] === selector[key])
            if (matches) {
              relationships.push({
                sourceId: `${namespace}/${pod.metadata?.name}`,
                targetId: providerId,
                type: 'exposed_by',
                metadata: {
                  podName: pod.metadata?.name,
                },
              })
            }
          }
        }
      } else if (resource.type === 'deployment') {
        // Find pods managed by this deployment
        const selector = (resource.metadata as any)?.selector || {}
        const podsResponse = await this.k8sApi.listNamespacedPod(namespace)
        if (podsResponse.body.items) {
          for (const pod of podsResponse.body.items) {
            const podLabels = pod.metadata?.labels || {}
            const matches = Object.keys(selector).every(key => podLabels[key] === selector[key])
            if (matches) {
              relationships.push({
                sourceId: providerId,
                targetId: `${namespace}/${pod.metadata?.name}`,
                type: 'manages',
                metadata: {
                  podName: pod.metadata?.name,
                },
              })
            }
          }
        }
      }
    } catch (error) {
      logger.error(`Error getting Kubernetes relationships for ${providerId}`, { error, providerId })
    }

    return relationships
  }

  async healthCheck(): Promise<HealthStatus> {
    try {
      const code = await this.k8sApi.getCode()
      if (code === 200) {
        return {
          status: 'healthy',
          lastChecked: new Date(),
        }
      }

      return {
        status: 'unhealthy',
        message: `API returned status ${code}`,
        lastChecked: new Date(),
      }
    } catch (error) {
      return {
        status: 'unhealthy',
        message: error instanceof Error ? error.message : 'Unknown error',
        lastChecked: new Date(),
      }
    }
  }
}

