/**
 * Proxmox VE Adapter
 * Implements the InfrastructureAdapter interface for Proxmox
 */

import { InfrastructureAdapter, NormalizedResource, ResourceSpec, NormalizedMetrics, TimeRange, HealthStatus, NormalizedRelationship } from '../types.js'
import { ResourceProvider } from '../../types/resource.js'
import { logger } from '../../lib/logger.js'
import type { ProxmoxCluster, ProxmoxVM, ProxmoxVMConfig } from './types.js'

/**
 * Proxmox VE Infrastructure Adapter
 * 
 * Implements the InfrastructureAdapter interface for Proxmox VE infrastructure.
 * Provides resource discovery, creation, update, deletion, metrics, and health checks.
 * 
 * @example
 * ```typescript
 * const adapter = new ProxmoxAdapter({
 *   apiUrl: 'https://proxmox.example.com:8006',
 *   apiToken: 'token-id=...'
 * });
 * const resources = await adapter.discoverResources();
 * ```
 */
export class ProxmoxAdapter implements InfrastructureAdapter {
  readonly provider: ResourceProvider = 'PROXMOX'
  
  private apiUrl: string
  private apiToken: string

  /**
   * Create a new Proxmox adapter instance
   * 
   * @param config - Configuration object
   * @param config.apiUrl - Proxmox API URL (e.g., 'https://proxmox.example.com:8006')
   * @param config.apiToken - Proxmox API token in format 'token-id=...' or 'username@realm!token-id=...'
   */
  constructor(config: { apiUrl: string; apiToken: string }) {
    this.apiUrl = config.apiUrl
    this.apiToken = config.apiToken
  }

  /**
   * Discover all resources across all Proxmox nodes
   * 
   * @returns Array of normalized resources (VMs) from all nodes
   * @throws {Error} If API connection fails or nodes cannot be retrieved
   * @example
   * ```typescript
   * const resources = await adapter.discoverResources();
   * console.log(`Found ${resources.length} VMs`);
   * ```
   */
  async discoverResources(): Promise<NormalizedResource[]> {
    try {
      const nodes = await this.getNodes()
      const allResources: NormalizedResource[] = []

      for (const node of nodes) {
        try {
          const vms = await this.getVMs(node.node)
          for (const vm of vms) {
            allResources.push(this.normalizeVM(vm, node.node))
          }
        } catch (error) {
          logger.error(`Error discovering VMs on node ${node.node}`, { error, node: node.node })
        }
      }

      return allResources
    } catch (error) {
      logger.error('Error discovering Proxmox resources', { error })
      throw error
    }
  }

  private async getNodes(): Promise<any[]> {
    try {
      const response = await fetch(`${this.apiUrl}/api2/json/nodes`, {
        method: 'GET',
        headers: {
          'Authorization': `PVEAPIToken ${this.apiToken}`, // Note: space after PVEAPIToken for Proxmox API
          'Content-Type': 'application/json',
        },
      })

      if (!response.ok) {
        const errorBody = await response.text().catch(() => '')
        logger.error('Failed to get Proxmox nodes', {
          status: response.status,
          statusText: response.statusText,
          body: errorBody,
          url: `${this.apiUrl}/api2/json/nodes`,
        })
        throw new Error(`Proxmox API error: ${response.status} ${response.statusText} - ${errorBody}`)
      }

      const data = await response.json()
      if (!data || !Array.isArray(data.data)) {
        logger.warn('Unexpected response format from Proxmox nodes API', { data })
        return []
      }
      return data.data
    } catch (error) {
      logger.error('Error getting Proxmox nodes', { error, apiUrl: this.apiUrl })
      throw error
    }
  }

  private async getVMs(node: string): Promise<any[]> {
    if (!node || typeof node !== 'string') {
      throw new Error(`Invalid node name: ${node}`)
    }

    try {
      const url = `${this.apiUrl}/api2/json/nodes/${encodeURIComponent(node)}/qemu`
      const response = await fetch(url, {
        method: 'GET',
        headers: {
          'Authorization': `PVEAPIToken ${this.apiToken}`, // Note: space after PVEAPIToken for Proxmox API
          'Content-Type': 'application/json',
        },
      })

      if (!response.ok) {
        const errorBody = await response.text().catch(() => '')
        logger.error('Failed to get VMs from Proxmox node', {
          node,
          status: response.status,
          statusText: response.statusText,
          body: errorBody,
          url,
        })
        throw new Error(`Proxmox API error getting VMs from node ${node}: ${response.status} ${response.statusText}`)
      }

      const data = await response.json()
      if (!data || !Array.isArray(data.data)) {
        logger.warn('Unexpected response format from Proxmox VMs API', { node, data })
        return []
      }
      return data.data
    } catch (error) {
      logger.error('Error getting VMs from Proxmox node', { error, node, apiUrl: this.apiUrl })
      throw error
    }
  }

  async getResource(providerId: string): Promise<NormalizedResource | null> {
    if (!providerId || typeof providerId !== 'string') {
      logger.warn('Invalid providerId provided to getResource', { providerId })
      return null
    }

    try {
      const [node, vmid] = providerId.split(':')
      if (!node || !vmid) {
        logger.warn('Invalid providerId format, expected "node:vmid"', { providerId })
        return null
      }

      // Validate vmid is numeric
      const vmidNum = parseInt(vmid, 10)
      if (isNaN(vmidNum) || vmidNum < 100 || vmidNum > 999999999) {
        logger.warn('Invalid VMID in providerId', { providerId, vmid, vmidNum })
        return null
      }

      const url = `${this.apiUrl}/api2/json/nodes/${encodeURIComponent(node)}/qemu/${encodeURIComponent(vmid)}`
      const response = await fetch(url, {
        method: 'GET',
        headers: {
          'Authorization': `PVEAPIToken ${this.apiToken}`, // Note: space after PVEAPIToken for Proxmox API
          'Content-Type': 'application/json',
        },
      })

      if (!response.ok) {
        if (response.status === 404) {
          logger.debug('VM not found', { providerId, node, vmid })
          return null
        }
        const errorBody = await response.text().catch(() => '')
        logger.error('Failed to get Proxmox resource', {
          providerId,
          node,
          vmid,
          status: response.status,
          statusText: response.statusText,
          body: errorBody,
          url,
        })
        throw new Error(`Proxmox API error getting resource ${providerId}: ${response.status} ${response.statusText}`)
      }

      const data = await response.json()
      if (!data || !data.data) {
        logger.warn('Empty response from Proxmox API', { providerId, data })
        return null
      }

      return this.normalizeVM(data.data, node)
    } catch (error) {
      logger.error(`Error getting Proxmox resource ${providerId}`, { error, providerId })
      return null
    }
  }

  async createResource(spec: ResourceSpec): Promise<NormalizedResource> {
    if (!spec || !spec.name) {
      throw new Error('Invalid resource spec: name is required')
    }

    try {
      const nodes = await this.getNodes()
      if (!nodes || nodes.length === 0) {
        throw new Error('No Proxmox nodes available')
      }

      // Find first online node, or use first node if status unknown
      const node = nodes.find((n: any) => n.status === 'online') || nodes[0]
      if (!node || !node.node) {
        throw new Error('No valid Proxmox node found')
      }

      const targetNode = node.node

      // Validate config
      if (spec.config?.vmid && (spec.config.vmid < 100 || spec.config.vmid > 999999999)) {
        throw new Error(`Invalid VMID: ${spec.config.vmid} (must be between 100 and 999999999)`)
      }

      const config: any = {
        vmid: spec.config?.vmid || undefined, // Auto-assign if not specified
        name: spec.name,
        cores: spec.config?.cores || 2,
        memory: spec.config?.memory || 2048,
        net0: spec.config?.net0 || 'virtio,bridge=vmbr0',
        ostype: spec.config?.ostype || 'l26',
      }

      // Validate memory is positive
      if (config.memory <= 0) {
        throw new Error(`Invalid memory value: ${config.memory} (must be positive)`)
      }

      // Create VM
      const url = `${this.apiUrl}/api2/json/nodes/${encodeURIComponent(targetNode)}/qemu`
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Authorization': `PVEAPIToken ${this.apiToken}`, // Note: space after PVEAPIToken for Proxmox API
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(config),
      })

      if (!response.ok) {
        const errorBody = await response.text().catch(() => '')
        logger.error('Failed to create Proxmox VM', {
          spec,
          node: targetNode,
          status: response.status,
          statusText: response.statusText,
          body: errorBody,
          url,
        })
        throw new Error(`Failed to create VM: ${response.status} ${response.statusText} - ${errorBody}`)
      }

      const data = await response.json()
      // VMID can be returned as string or number from Proxmox API
      const vmid = data.data || config.vmid

      if (!vmid) {
        throw new Error('VM creation succeeded but no VMID returned')
      }

      const vmidStr = String(vmid) // Ensure it's a string for providerId format

      // Get created VM with retry logic (VM may not be immediately available)
      let retries = 3
      while (retries > 0) {
        const vm = await this.getResource(`${targetNode}:${vmidStr}`)
        if (vm) {
          return vm
        }
        await new Promise(resolve => setTimeout(resolve, 1000)) // Wait 1 second
        retries--
      }

      throw new Error(`VM ${vmidStr} created but not found after retries`)
    } catch (error) {
      logger.error('Error creating Proxmox resource', { error })
      throw error
    }
  }

  async updateResource(providerId: string, spec: Partial<ResourceSpec>): Promise<NormalizedResource> {
    if (!providerId || typeof providerId !== 'string') {
      throw new Error(`Invalid providerId: ${providerId}`)
    }

    try {
      const [node, vmid] = providerId.split(':')
      if (!node || !vmid) {
        throw new Error(`Invalid provider ID format, expected "node:vmid", got: ${providerId}`)
      }

      // Validate vmid is numeric
      const vmidNum = parseInt(vmid, 10)
      if (isNaN(vmidNum) || vmidNum < 100 || vmidNum > 999999999) {
        throw new Error(`Invalid VMID in providerId: ${vmid}`)
      }

      const updates: any = {}
      if (spec.config?.cores !== undefined) {
        if (spec.config.cores < 1) {
          throw new Error(`Invalid CPU cores: ${spec.config.cores} (must be at least 1)`)
        }
        updates.cores = spec.config.cores
      }
      if (spec.config?.memory !== undefined) {
        if (spec.config.memory <= 0) {
          throw new Error(`Invalid memory: ${spec.config.memory} (must be positive)`)
        }
        updates.memory = spec.config.memory
      }

      if (Object.keys(updates).length === 0) {
        logger.debug('No updates to apply', { providerId })
        return this.getResource(providerId) as Promise<NormalizedResource>
      }

      const url = `${this.apiUrl}/api2/json/nodes/${encodeURIComponent(node)}/qemu/${encodeURIComponent(vmid)}/config`
      const response = await fetch(url, {
        method: 'PUT',
        headers: {
          'Authorization': `PVEAPIToken ${this.apiToken}`, // Note: space after PVEAPIToken for Proxmox API
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(updates),
      })

      if (!response.ok) {
        const errorBody = await response.text().catch(() => '')
        logger.error('Failed to update Proxmox VM', {
          providerId,
          node,
          vmid,
          updates,
          status: response.status,
          statusText: response.statusText,
          body: errorBody,
          url,
        })
        throw new Error(`Failed to update VM ${providerId}: ${response.status} ${response.statusText} - ${errorBody}`)
      }

      return this.getResource(providerId) as Promise<NormalizedResource>
    } catch (error) {
      logger.error(`Error updating Proxmox resource ${providerId}`, { error, providerId })
      throw error
    }
  }

  async deleteResource(providerId: string): Promise<boolean> {
    if (!providerId || typeof providerId !== 'string') {
      logger.warn('Invalid providerId provided to deleteResource', { providerId })
      return false
    }

    try {
      const [node, vmid] = providerId.split(':')
      if (!node || !vmid) {
        logger.warn('Invalid provider ID format, expected "node:vmid"', { providerId })
        return false
      }

      // Validate vmid is numeric
      const vmidNum = parseInt(vmid, 10)
      if (isNaN(vmidNum) || vmidNum < 100 || vmidNum > 999999999) {
        logger.warn('Invalid VMID in providerId', { providerId, vmid })
        return false
      }

      const url = `${this.apiUrl}/api2/json/nodes/${encodeURIComponent(node)}/qemu/${encodeURIComponent(vmid)}`
      const response = await fetch(url, {
        method: 'DELETE',
        headers: {
          'Authorization': `PVEAPIToken ${this.apiToken}`, // Note: space after PVEAPIToken for Proxmox API
          'Content-Type': 'application/json',
        },
      })

      if (!response.ok) {
        const errorBody = await response.text().catch(() => '')
        logger.error('Failed to delete Proxmox VM', {
          providerId,
          node,
          vmid,
          status: response.status,
          statusText: response.statusText,
          body: errorBody,
          url,
        })
        return false
      }

      return true
    } catch (error) {
      logger.error(`Error deleting Proxmox resource ${providerId}`, { error, providerId })
      return false
    }
  }

  async getMetrics(providerId: string, timeRange: TimeRange): Promise<NormalizedMetrics[]> {
    try {
      const [node, vmid] = providerId.split(':')
      if (!node || !vmid) return []

      const response = await fetch(
        `${this.apiUrl}/api2/json/nodes/${node}/qemu/${vmid}/rrddata?timeframe=hour&cf=AVERAGE`,
        {
          method: 'GET',
          headers: {
            'Authorization': `PVEAPIToken ${this.apiToken}`, // Note: space after PVEAPIToken for Proxmox API
            'Content-Type': 'application/json',
          },
        }
      )

      if (!response.ok) return []

      const data = await response.json()
      const metrics: NormalizedMetrics[] = []

      if (data.data && Array.isArray(data.data)) {
        for (const point of data.data) {
          if (point.cpu) {
            metrics.push({
              resourceId: providerId,
              metricType: 'CPU_USAGE',
              value: parseFloat(point.cpu) * 100,
              timestamp: new Date(point.time * 1000),
            })
          }
          if (point.mem) {
            metrics.push({
              resourceId: providerId,
              metricType: 'MEMORY_USAGE',
              value: parseFloat(point.mem),
              timestamp: new Date(point.time * 1000),
            })
          }
        }
      }

      return metrics
    } catch (error) {
      logger.error(`Error getting Proxmox metrics for ${providerId}`, { error, providerId })
      return []
    }
  }

  async getRelationships(providerId: string): Promise<NormalizedRelationship[]> {
    try {
      const [node, vmid] = providerId.split(':')
      if (!node || !vmid) return []

      const vm = await this.getResource(providerId)
      if (!vm) return []

      const relationships: NormalizedRelationship[] = [
        {
          sourceId: providerId,
          targetId: `proxmox-node-${node}`,
          type: 'HOSTED_ON',
          metadata: { node },
        },
      ]

      // Add storage relationships if available
      if (vm.metadata?.storage) {
        relationships.push({
          sourceId: providerId,
          targetId: `proxmox-storage-${vm.metadata.storage}`,
          type: 'USES_STORAGE',
          metadata: { storage: vm.metadata.storage },
        })
      }

      return relationships
    } catch (error) {
      logger.error(`Error getting Proxmox relationships for ${providerId}`, { error, providerId })
      return []
    }
  }

  async healthCheck(): Promise<HealthStatus> {
    try {
      const response = await fetch(`${this.apiUrl}/api2/json/version`, {
        method: 'GET',
        headers: {
          'Authorization': `PVEAPIToken ${this.apiToken}`, // Note: space after PVEAPIToken for Proxmox API
          'Content-Type': 'application/json',
        },
      })

      if (response.ok) {
        return {
          status: 'healthy',
          lastChecked: new Date(),
        }
      }

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

  /**
   * Helper method to normalize Proxmox VM to unified resource format
   */
  private normalizeVM(vm: ProxmoxVM, node: string): NormalizedResource {
    return {
      id: `proxmox-${node}-${vm.vmid}`,
      name: vm.name || `VM ${vm.vmid}`,
      type: 'virtual_machine',
      provider: 'PROXMOX',
      providerId: `${node}:${vm.vmid}`,
      providerResourceId: `proxmox://${node}/vm/${vm.vmid}`,
      status: vm.status,
      metadata: {
        node,
        vmid: vm.vmid,
        cpu: vm.cpu,
        memory: vm.mem,
        disk: vm.disk,
        uptime: vm.uptime,
      },
      tags: [],
      createdAt: new Date(Date.now() - vm.uptime * 1000),
      updatedAt: new Date(),
    }
  }
}

