/**
 * Netcup SCP API Service
 *
 * Provides integration with Netcup Server Control Panel API for server management.
 * Uses OAuth2 Device Flow for authentication.
 *
 * Base URL: https://www.servercontrolpanel.de/scp-core
 * Auth: OAuth2 Device Flow via OIDC
 */

import { settingsService } from './settings-service'

// API Configuration
const NETCUP_BASE_URL = 'https://www.servercontrolpanel.de/scp-core'
const NETCUP_AUTH_URL = 'https://www.servercontrolpanel.de/realms/scp/protocol/openid-connect'
const CLIENT_ID = 'scp'
const SCOPE = 'offline_access openid'

// Types

export interface DeviceAuthResponse {
  device_code: string
  user_code: string
  verification_uri: string
  verification_uri_complete: string
  expires_in: number
  interval: number
}

export interface TokenResponse {
  access_token: string
  refresh_token: string
  expires_in: number
  token_type: string
}

export interface NetcupServer {
  id: string
  name: string
  nickname?: string
  state: 'ON' | 'OFF' | 'POWERCYCLE' | 'RESET' | 'POWEROFF' | 'UNKNOWN'
  primaryIpv4?: string
  primaryIpv6?: string
  hostname?: string
  cpuCores?: number
  ramGb?: number
  diskGb?: number
  // Additional fields when loadLiveInfo=true
  cpuUsage?: number
  ramUsage?: number
  diskUsage?: number
}

// Server live info response from Netcup API (only returned with loadServerLiveInfo=true)
interface NetcupServerLiveInfo {
  state?: string  // RUNNING, SHUTOFF, PAUSED, etc.
  maxCpuCount?: number
  cpuCount?: number
  memoryInMiB?: number
  availableMemoryInMiB?: number
}

// Raw server response from Netcup API (actual field names from their API)
interface NetcupServerRaw {
  id: number
  name: string
  disabled?: boolean
  hostname?: string
  nickname?: string
  template?: {
    id: number
    name: string
  }
  // Live info object only available with loadServerLiveInfo=true
  serverLiveInfo?: NetcupServerLiveInfo
  // Legacy state field (may not be used anymore)
  state?: string
  // IP addresses - Netcup might use various field names
  ipv4?: string
  ipv6?: string
  ip?: string
  primaryIpv4?: string
  primaryIpv6?: string
  // Hardware specs - various possible field names
  cpu?: number
  ram?: number
  hdd?: number
  // Additional raw fields (allows for undocumented API fields)
  [key: string]: unknown
}

/**
 * Map Netcup API state to our normalized state
 */
function normalizeServerState(rawState?: string): NetcupServer['state'] {
  if (!rawState) return 'UNKNOWN'

  const state = rawState.toUpperCase()

  // Map common variations
  switch (state) {
    case 'ON':
    case 'RUNNING':
    case 'ONLINE':
      return 'ON'
    case 'OFF':
    case 'STOPPED':
    case 'OFFLINE':
    case 'SHUTOFF':
      return 'OFF'
    case 'POWERCYCLE':
    case 'CYCLING':
      return 'POWERCYCLE'
    case 'RESET':
    case 'RESETTING':
      return 'RESET'
    case 'POWEROFF':
    case 'SHUTTING_DOWN':
    case 'STOPPING':
      return 'POWEROFF'
    default:
      return 'UNKNOWN'
  }
}

/**
 * Transform raw Netcup API server response to our format
 */
function transformServer(raw: NetcupServerRaw): NetcupServer {
  // Handle potential alternate field names from Netcup API
  // Get IPv4 from ipv4Addresses array if available
  const ipv4Addresses = (raw as Record<string, unknown>)['ipv4Addresses'] as Array<{ ip: string }> | undefined
  const ipv4 = raw.ipv4 || raw.ip || raw.primaryIpv4 ||
               (ipv4Addresses && ipv4Addresses.length > 0 ? ipv4Addresses[0].ip : undefined) ||
               (raw as Record<string, unknown>)['ipAddress'] as string || undefined
  const ipv6 = raw.ipv6 || raw.primaryIpv6 || (raw as Record<string, unknown>)['ipv6Address'] as string || undefined

  // CPU cores - check root level maxCpuCount first (from Netcup API), then serverLiveInfo
  const cpuCores = (raw as Record<string, unknown>)['maxCpuCount'] as number ||
                   raw.serverLiveInfo?.cpuCount ||
                   raw.serverLiveInfo?.maxCpuCount ||
                   raw.cpu ||
                   (raw as Record<string, unknown>)['cpuCores'] as number ||
                   (raw as Record<string, unknown>)['cores'] as number || undefined

  // RAM - check serverLiveInfo.currentServerMemoryInMiB (Netcup API field name)
  const serverLiveInfo = raw.serverLiveInfo as Record<string, unknown> | undefined
  const ramMiB = (serverLiveInfo?.['currentServerMemoryInMiB'] as number) ||
                 raw.serverLiveInfo?.memoryInMiB ||
                 raw.ram ||
                 (raw as Record<string, unknown>)['memory'] as number ||
                 (raw as Record<string, unknown>)['ramMb'] as number || undefined
  const ramGb = ramMiB ? Math.round(ramMiB / 1024) : undefined

  // Disk - check serverLiveInfo.disks array for capacity, or disksAvailableSpaceInMiB
  const disksArray = serverLiveInfo?.['disks'] as Array<{ capacityInMiB: number }> | undefined
  const diskCapacityMiB = (disksArray && disksArray.length > 0 ? disksArray[0].capacityInMiB : undefined) ||
                          (raw as Record<string, unknown>)['disksAvailableSpaceInMiB'] as number || undefined
  const diskGb = diskCapacityMiB ? Math.round(diskCapacityMiB / 1024) :
                 (raw.hdd || (raw as Record<string, unknown>)['disk'] as number ||
                  (raw as Record<string, unknown>)['storage'] as number ||
                  (raw as Record<string, unknown>)['diskGb'] as number || undefined)

  // Extract state from serverLiveInfo (new API) or fallback to root state (legacy)
  const rawState = raw.serverLiveInfo?.state || raw.state

  return {
    id: String(raw.id),
    name: raw.name,
    nickname: raw.nickname || undefined,
    state: normalizeServerState(rawState),
    primaryIpv4: ipv4,
    primaryIpv6: ipv6,
    hostname: raw.hostname || undefined,
    cpuCores,
    ramGb,
    diskGb,
  }
}

// Raw API response from Netcup
interface NetcupImageFlavourRaw {
  id: number
  name: string      // variant name like "minimal", "standard"
  alias: string
  text: string      // description
  image: {
    id: number
    name: string    // OS name like "Debian", "Ubuntu"
  }
}

// Our normalized interface
export interface ImageFlavour {
  id: string
  name: string        // Display name: "Debian 12 - minimal"
  description?: string
  category: string    // OS name for grouping
}

export interface RescueStatus {
  active: boolean
  password?: string
  expiresAt?: string
}

export interface RescueCredentials {
  password: string
  expiresAt: string
}

export interface TaskResponse {
  taskId: string
  status: 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED'
  message?: string
}

export interface TaskStatus {
  taskId: string
  status: 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED'
  progress?: number
  message?: string
  result?: unknown
  error?: string
}

export type PowerAction = 'ON' | 'OFF' | 'POWERCYCLE' | 'RESET' | 'POWEROFF'

// Metrics types
export interface MetricsDataPoint {
  timestamp: string
  value: number
}

export interface CpuMetrics {
  dataPoints: MetricsDataPoint[]
  average: number
  max: number
}

export interface DiskMetrics {
  readBps: MetricsDataPoint[]
  writeBps: MetricsDataPoint[]
  readIops: MetricsDataPoint[]
  writeIops: MetricsDataPoint[]
}

export interface NetworkMetrics {
  rxBps: MetricsDataPoint[]
  txBps: MetricsDataPoint[]
}

export interface ServerMetrics {
  cpu: CpuMetrics
  disk: DiskMetrics
  network: NetworkMetrics
  period: string // e.g., "1h", "24h", "7d"
}

// Snapshot types
export interface Snapshot {
  name: string
  createdAt: string
  size?: number
  description?: string
}

export interface SnapshotCreateResult {
  success: boolean
  name: string
  message?: string
}

// Error classes

export class NetcupAuthError extends Error {
  constructor(message: string) {
    super(message)
    this.name = 'NetcupAuthError'
  }
}

export class NetcupApiError extends Error {
  constructor(
    message: string,
    public statusCode: number,
    public details?: unknown
  ) {
    super(message)
    this.name = 'NetcupApiError'
  }
}

// Service class

class NetcupService {
  private static instance: NetcupService

  private constructor() {}

  static getInstance(): NetcupService {
    if (!NetcupService.instance) {
      NetcupService.instance = new NetcupService()
    }
    return NetcupService.instance
  }

  // ========== OAuth2 Device Flow ==========

  /**
   * Initiate OAuth2 Device Flow
   * Returns user_code and verification URL for user to authorize
   */
  async initiateDeviceAuth(): Promise<DeviceAuthResponse> {
    const response = await fetch(`${NETCUP_AUTH_URL}/auth/device`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({
        client_id: CLIENT_ID,
        scope: SCOPE,
      }),
    })

    if (!response.ok) {
      const error = await response.text()
      throw new NetcupAuthError(`Failed to initiate device auth: ${error}`)
    }

    return response.json()
  }

  /**
   * Poll for token after user authorizes device
   * Returns tokens when authorization is complete
   */
  async pollForToken(deviceCode: string): Promise<TokenResponse | null> {
    const response = await fetch(`${NETCUP_AUTH_URL}/token`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({
        client_id: CLIENT_ID,
        grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
        device_code: deviceCode,
      }),
    })

    if (response.status === 400) {
      const error = await response.json()
      if (error.error === 'authorization_pending') {
        return null // User hasn't authorized yet
      }
      if (error.error === 'slow_down') {
        return null // Need to poll slower
      }
      throw new NetcupAuthError(`Token exchange failed: ${error.error_description || error.error}`)
    }

    if (!response.ok) {
      const error = await response.text()
      throw new NetcupAuthError(`Token exchange failed: ${error}`)
    }

    const tokens: TokenResponse = await response.json()

    // Store tokens
    await this.storeTokens(tokens)

    return tokens
  }

  /**
   * Refresh access token using refresh token
   */
  async refreshAccessToken(): Promise<TokenResponse> {
    const refreshToken = await settingsService.get('netcup.refresh_token')

    if (!refreshToken) {
      throw new NetcupAuthError('No refresh token available. Please re-authenticate.')
    }

    const response = await fetch(`${NETCUP_AUTH_URL}/token`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({
        client_id: CLIENT_ID,
        grant_type: 'refresh_token',
        refresh_token: refreshToken,
      }),
    })

    if (!response.ok) {
      const error = await response.text()
      // Clear stored tokens on refresh failure
      await this.clearTokens()
      throw new NetcupAuthError(`Token refresh failed: ${error}`)
    }

    const tokens: TokenResponse = await response.json()

    // Store new tokens
    await this.storeTokens(tokens)

    return tokens
  }

  /**
   * Store tokens in settings
   */
  private async storeTokens(tokens: TokenResponse): Promise<void> {
    const expiresAt = new Date(Date.now() + tokens.expires_in * 1000).toISOString()

    await Promise.all([
      settingsService.set('netcup.access_token', tokens.access_token),
      settingsService.set('netcup.refresh_token', tokens.refresh_token),
      settingsService.set('netcup.token_expires_at', expiresAt),
    ])
  }

  /**
   * Clear stored tokens
   */
  async clearTokens(): Promise<void> {
    await Promise.all([
      settingsService.delete('netcup.access_token'),
      settingsService.delete('netcup.refresh_token'),
      settingsService.delete('netcup.token_expires_at'),
    ])
  }

  /**
   * Get valid access token (refreshes if needed)
   */
  async getAccessToken(): Promise<string> {
    const [accessToken, expiresAtStr] = await Promise.all([
      settingsService.get('netcup.access_token'),
      settingsService.get('netcup.token_expires_at'),
    ])

    if (!accessToken) {
      throw new NetcupAuthError('Not authenticated with Netcup. Please connect your account.')
    }

    // Check if token is expired (with 60s buffer)
    const expiresAt = expiresAtStr ? new Date(expiresAtStr) : new Date(0)
    const isExpired = expiresAt.getTime() - 60000 < Date.now()

    if (isExpired) {
      const tokens = await this.refreshAccessToken()
      return tokens.access_token
    }

    return accessToken
  }

  /**
   * Check if authenticated with Netcup
   */
  async isAuthenticated(): Promise<boolean> {
    try {
      const refreshToken = await settingsService.get('netcup.refresh_token')
      return !!refreshToken
    } catch {
      return false
    }
  }

  /**
   * Get authentication status
   */
  async getAuthStatus(): Promise<{
    authenticated: boolean
    expiresAt: string | null
  }> {
    const [refreshToken, expiresAt] = await Promise.all([
      settingsService.get('netcup.refresh_token'),
      settingsService.get('netcup.token_expires_at'),
    ])

    return {
      authenticated: !!refreshToken,
      expiresAt: expiresAt,
    }
  }

  // ========== API Request Helper ==========

  private async apiRequest<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const accessToken = await this.getAccessToken()
    const fullUrl = `${NETCUP_BASE_URL}${endpoint}`

    // Build headers as plain object (more reliable in Next.js server context)
    const headers: Record<string, string> = {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    }

    // Add any custom headers from options
    if (options.headers) {
      const optHeaders = options.headers as Record<string, string>
      Object.assign(headers, optHeaders)
    }

    console.log('Netcup API Request:', {
      url: fullUrl,
      method: options.method || 'GET',
      headers: { ...headers, Authorization: 'Bearer [REDACTED]' },
      body: options.body,
    })

    const response = await fetch(fullUrl, {
      method: options.method || 'GET',
      body: options.body,
      headers,
      cache: 'no-store',
    })

    if (!response.ok) {
      const error = await response.text()
      throw new NetcupApiError(
        `API request failed: ${error}`,
        response.status,
        error
      )
    }

    // Handle empty responses
    const text = await response.text()
    if (!text) {
      return {} as T
    }

    return JSON.parse(text)
  }

  // ========== Server Management ==========

  /**
   * Get interfaces (IP addresses) for a server
   */
  async getServerInterfaces(serverId: string): Promise<{ ipv4?: string; ipv6?: string }> {
    try {
      // Netcup API returns array of interfaces
      // Each interface has 'ipv4Addresses' and 'ipv6Addresses' arrays
      interface NetcupIpEntry {
        ip: string
        cidr?: string
        gateway?: string
        rdns?: string | Record<string, unknown>
        type?: string
        id?: number
        interfaceMac?: string
        editable?: boolean
        destinationIp?: string | null
        linkLocal?: boolean
        networkPrefix?: string
      }

      interface NetcupInterface {
        mac?: string
        driver?: string
        speedInMBits?: number
        ipv4Addresses?: NetcupIpEntry[]
        ipv6Addresses?: NetcupIpEntry[]
        // Legacy formats (kept for compatibility)
        ips?: { ip: string; version: 4 | 6 }[]
        addresses?: { ip: string; version: 4 | 6 }[]
        ipv4?: string
        ipv6?: string
        primaryIpv4?: string
        primaryIpv6?: string
        ip?: string
        [key: string]: unknown
      }

      const rawResponse = await this.apiRequest<NetcupInterface[] | { interfaces: NetcupInterface[] }>(`/api/v1/servers/${serverId}/interfaces`)

      // Handle both array response and wrapped response
      const interfaces: NetcupInterface[] = Array.isArray(rawResponse)
        ? rawResponse
        : (rawResponse?.interfaces || [])

      // Extract primary IPs from interfaces
      let ipv4: string | undefined
      let ipv6: string | undefined

      for (const iface of interfaces) {
        // Check 'ipv4Addresses' array (actual Netcup format)
        if (!ipv4 && iface.ipv4Addresses && Array.isArray(iface.ipv4Addresses)) {
          const firstIpv4 = iface.ipv4Addresses.find(entry => entry.ip && !entry.ip.startsWith('fe80'))
          if (firstIpv4) {
            ipv4 = firstIpv4.ip
          }
        }

        // Check 'ipv6Addresses' array - prefer non-link-local addresses
        if (!ipv6 && iface.ipv6Addresses && Array.isArray(iface.ipv6Addresses)) {
          // Prefer non-link-local IPv6 addresses
          const nonLinkLocal = iface.ipv6Addresses.find(entry => entry.ip && !entry.linkLocal && !entry.ip?.startsWith('fe80'))
          if (nonLinkLocal) {
            ipv6 = nonLinkLocal.networkPrefix || nonLinkLocal.ip
          }
        }

        // Legacy: check 'ips' array format
        if (iface.ips && Array.isArray(iface.ips)) {
          for (const ipEntry of iface.ips) {
            if (!ipv4 && ipEntry.version === 4) {
              ipv4 = ipEntry.ip
            }
            if (!ipv6 && ipEntry.version === 6) {
              ipv6 = ipEntry.ip
            }
          }
        }

        // Legacy: check 'addresses' array format
        if (iface.addresses && Array.isArray(iface.addresses)) {
          for (const addr of iface.addresses) {
            if (!ipv4 && addr.version === 4) {
              ipv4 = addr.ip
            }
            if (!ipv6 && addr.version === 6) {
              ipv6 = addr.ip
            }
          }
        }

        // Fallback: direct field names
        if (!ipv4) {
          ipv4 = iface.primaryIpv4 || iface.ipv4 || iface.ip
        }
        if (!ipv6) {
          ipv6 = iface.primaryIpv6 || iface.ipv6
        }
      }

      return { ipv4, ipv6 }
    } catch (error) {
      console.error(`[Netcup] Failed to get interfaces for server ${serverId}:`, error)
      return {}
    }
  }

  /**
   * Get list of all servers (with IPs from interfaces)
   * @param loadLiveInfo If true, fetches live status (ON/OFF) for each server - slower but accurate
   */
  async getServers(loadLiveInfo = false): Promise<NetcupServer[]> {
    // Netcup API returns array directly, not wrapped in { servers: [...] }
    const rawServers = await this.apiRequest<NetcupServerRaw[]>('/api/v1/servers')
    const servers = rawServers.map(transformServer)

    // Fetch interfaces (and optionally live info) for each server in parallel
    const serversWithData = await Promise.all(
      servers.map(async (server) => {
        if (loadLiveInfo) {
          // Get full server details with live info (includes state)
          try {
            const liveServer = await this.getServer(server.id, true)
            return liveServer
          } catch (error) {
            console.error(`[Netcup] Failed to get live info for server ${server.id}:`, error)
            // Fall back to basic info with interfaces
            const { ipv4, ipv6 } = await this.getServerInterfaces(server.id)
            return {
              ...server,
              primaryIpv4: server.primaryIpv4 || ipv4,
              primaryIpv6: server.primaryIpv6 || ipv6,
            }
          }
        } else {
          // Just get IPs without live info
          const { ipv4, ipv6 } = await this.getServerInterfaces(server.id)
          return {
            ...server,
            primaryIpv4: server.primaryIpv4 || ipv4,
            primaryIpv6: server.primaryIpv6 || ipv6,
          }
        }
      })
    )

    return serversWithData
  }

  /**
   * Get server details
   * @param loadLiveInfo If true, fetches live status (RUNNING/SHUTOFF) from serverLiveInfo
   */
  async getServer(serverId: string, loadLiveInfo = false): Promise<NetcupServer> {
    // Netcup API uses loadServerLiveInfo parameter
    // The state is returned in serverLiveInfo.state field
    const params = loadLiveInfo ? '?loadServerLiveInfo=true' : '?loadServerLiveInfo=false'
    const rawServer = await this.apiRequest<NetcupServerRaw>(`/api/v1/servers/${serverId}${params}`)
    const server = transformServer(rawServer)

    // Also get interfaces to ensure we have IPs
    const { ipv4, ipv6 } = await this.getServerInterfaces(serverId)

    const result = {
      ...server,
      primaryIpv4: server.primaryIpv4 || ipv4,
      primaryIpv6: server.primaryIpv6 || ipv6,
    }
    console.log(`[Netcup Service] getServer(${serverId}) final state:`, result.state)
    return result
  }

  /**
   * Find server by IP address
   */
  async findServerByIp(ip: string): Promise<NetcupServer | null> {
    const servers = await this.getServers()
    return servers.find((s) => s.primaryIpv4 === ip || s.primaryIpv6 === ip) || null
  }

  /**
   * Perform power action on server
   * Note: Netcup API requires 'application/merge-patch+json' for PATCH operations
   *
   * API format per OpenAPI spec:
   * - Body: { state: "ON" } or { state: "OFF" }
   * - Query param stateOption: POWERCYCLE, RESET (for ON), POWEROFF (for OFF)
   *
   * Actions:
   * - ON: PATCH /servers/{id} with { state: "ON" }
   * - OFF: PATCH /servers/{id} with { state: "OFF" } (graceful shutdown via ACPI)
   * - POWERCYCLE: PATCH /servers/{id}?stateOption=POWERCYCLE with { state: "ON" }
   * - RESET: PATCH /servers/{id}?stateOption=RESET with { state: "ON" }
   * - POWEROFF: PATCH /servers/{id}?stateOption=POWEROFF with { state: "OFF" } (force)
   */
  async powerAction(serverId: string, action: PowerAction): Promise<void> {
    let state: 'ON' | 'OFF'
    let stateOption: string | undefined

    switch (action) {
      case 'ON':
        state = 'ON'
        break
      case 'OFF':
        state = 'OFF'
        break
      case 'POWERCYCLE':
        state = 'ON'
        stateOption = 'POWERCYCLE'
        break
      case 'RESET':
        state = 'ON'
        stateOption = 'RESET'
        break
      case 'POWEROFF':
        state = 'OFF'
        stateOption = 'POWEROFF'
        break
      default:
        throw new Error(`Unknown power action: ${action}`)
    }

    const url = stateOption
      ? `/api/v1/servers/${serverId}?stateOption=${stateOption}`
      : `/api/v1/servers/${serverId}`

    await this.apiRequest(url, {
      method: 'PATCH',
      body: JSON.stringify({ state }),
      headers: {
        'Content-Type': 'application/merge-patch+json',
      },
    })
  }

  /**
   * Update server hostname
   * Note: Netcup API requires 'application/merge-patch+json' for PATCH property updates
   */
  async updateHostname(serverId: string, hostname: string): Promise<void> {
    await this.apiRequest(`/api/v1/servers/${serverId}`, {
      method: 'PATCH',
      body: JSON.stringify({ hostname }),
      headers: {
        'Content-Type': 'application/merge-patch+json',
      },
    })
  }

  /**
   * Update server nickname
   * Note: Netcup API requires 'application/merge-patch+json' for PATCH property updates
   */
  async updateNickname(serverId: string, nickname: string): Promise<void> {
    await this.apiRequest(`/api/v1/servers/${serverId}`, {
      method: 'PATCH',
      body: JSON.stringify({ nickname }),
      headers: {
        'Content-Type': 'application/merge-patch+json',
      },
    })
  }

  // ========== Image/Reinstall ==========

  /**
   * Get available OS image flavours
   */
  async getImageFlavours(serverId: string): Promise<ImageFlavour[]> {
    // Netcup API returns raw format, we need to map it
    const raw = await this.apiRequest<NetcupImageFlavourRaw[]>(
      `/api/v1/servers/${serverId}/imageflavours`
    )

    // Map to our normalized interface
    return raw.map((f) => ({
      id: String(f.id),
      name: `${f.image.name} - ${f.name}`,  // e.g., "Debian 12 - minimal"
      description: f.text || undefined,
      category: f.image.name,  // Group by OS name
    }))
  }

  /**
   * Reinstall server with specified image
   * Note: Netcup emails the new root password to the account holder
   */
  async reinstallServer(serverId: string, imageFlavourId: string): Promise<TaskResponse> {
    return this.apiRequest<TaskResponse>(`/api/v1/servers/${serverId}/image`, {
      method: 'POST',
      body: JSON.stringify({
        imageFlavourId: parseInt(imageFlavourId, 10),
        emailToExecutingUser: true,  // Send credentials email
      }),
    })
  }

  // ========== Rescue Mode ==========

  /**
   * Get rescue mode status
   */
  async getRescueStatus(serverId: string): Promise<RescueStatus> {
    return this.apiRequest<RescueStatus>(`/api/v1/servers/${serverId}/rescuesystem`)
  }

  /**
   * Activate rescue mode
   */
  async activateRescue(serverId: string): Promise<RescueCredentials> {
    return this.apiRequest<RescueCredentials>(`/api/v1/servers/${serverId}/rescuesystem`, {
      method: 'POST',
    })
  }

  /**
   * Deactivate rescue mode
   */
  async deactivateRescue(serverId: string): Promise<void> {
    await this.apiRequest(`/api/v1/servers/${serverId}/rescuesystem`, {
      method: 'DELETE',
    })
  }

  // ========== Task Tracking ==========

  /**
   * Get task status
   */
  async getTask(taskId: string): Promise<TaskStatus> {
    return this.apiRequest<TaskStatus>(`/api/v1/tasks/${taskId}`)
  }

  /**
   * Cancel a running task
   */
  async cancelTask(taskId: string): Promise<void> {
    await this.apiRequest(`/api/v1/tasks/${taskId}:cancel`, {
      method: 'PUT',
    })
  }

  // ========== Metrics ==========

  /**
   * Get CPU metrics for a server
   * @param serverId Server ID
   * @param hours Number of hours of history (max 1440 = 60 days)
   */
  async getCpuMetrics(serverId: string, hours = 24): Promise<CpuMetrics> {
    // Netcup API returns: { "timestamp": { "CPU0": value, "CPU1": value, ... }, ... }
    // Values are CPU time in microseconds for a 10-minute interval
    // 10 min = 600,000,000 microseconds per core
    const INTERVAL_MICROSECONDS = 600_000_000

    const rawData = await this.apiRequest<Record<string, Record<string, number>>>(
      `/api/v1/servers/${serverId}/metrics/cpu?hours=${hours}`
    )

    const dataPoints: MetricsDataPoint[] = []

    // Process the timestamp-keyed object
    if (rawData && typeof rawData === 'object' && !Array.isArray(rawData)) {
      const timestamps = Object.keys(rawData).sort()

      for (const timestamp of timestamps) {
        const cpuValues = rawData[timestamp]
        if (cpuValues && typeof cpuValues === 'object') {
          // Sum all CPU core values
          const coreValues = Object.values(cpuValues).filter(v => typeof v === 'number')
          const numCores = coreValues.length

          if (numCores > 0) {
            const totalMicroseconds = coreValues.reduce((sum, val) => sum + val, 0)
            // Calculate percentage: (total_used / (num_cores * max_per_core)) * 100
            const percentage = (totalMicroseconds / (numCores * INTERVAL_MICROSECONDS)) * 100
            // Cap at 100% to handle edge cases where raw values exceed expected max
            const cappedPercentage = Math.min(percentage, 100)
            dataPoints.push({
              timestamp,
              value: Math.round(cappedPercentage * 100) / 100, // Round to 2 decimal places
            })
          }
        }
      }
    }

    // Calculate average and max from dataPoints
    const values = dataPoints.map(d => d.value)
    const average = values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : 0
    const max = values.length > 0 ? Math.max(...values) : 0

    return {
      dataPoints,
      average: Math.round(average * 100) / 100,
      max: Math.round(max * 100) / 100,
    }
  }

  /**
   * Get disk I/O metrics for a server
   * @param serverId Server ID
   * @param hours Number of hours of history (max 1440)
   */
  async getDiskMetrics(serverId: string, hours = 24): Promise<DiskMetrics> {
    // Netcup API returns: { "timestamp": { "disk_read_bytes": value, "disk_write_bytes": value, ... }, ... }
    const rawData = await this.apiRequest<Record<string, Record<string, number>>>(
      `/api/v1/servers/${serverId}/metrics/disk?hours=${hours}`
    )

    const readBps: MetricsDataPoint[] = []
    const writeBps: MetricsDataPoint[] = []
    const readIops: MetricsDataPoint[] = []
    const writeIops: MetricsDataPoint[] = []

    if (rawData && typeof rawData === 'object' && !Array.isArray(rawData)) {
      const timestamps = Object.keys(rawData).sort()

      for (const timestamp of timestamps) {
        const diskValues = rawData[timestamp]
        if (diskValues && typeof diskValues === 'object') {
          // Netcup returns: { "vda Read": value, "vda Write": value }
          // Find keys containing "Read" or "Write" (case insensitive)
          let readValue = 0
          let writeValue = 0

          for (const [key, val] of Object.entries(diskValues)) {
            const lowerKey = key.toLowerCase()
            if (lowerKey.includes('read') && typeof val === 'number') {
              readValue += val
            }
            if (lowerKey.includes('write') && typeof val === 'number') {
              writeValue += val
            }
          }

          readBps.push({ timestamp, value: Math.round(readValue * 100) / 100 })
          writeBps.push({ timestamp, value: Math.round(writeValue * 100) / 100 })
          // Note: Netcup disk metrics don't include IOPS, so these will be 0
          readIops.push({ timestamp, value: 0 })
          writeIops.push({ timestamp, value: 0 })
        }
      }
    }

    return { readBps, writeBps, readIops, writeIops }
  }

  /**
   * Get network metrics for a server
   * @param serverId Server ID
   * @param hours Number of hours of history (max 1440)
   */
  async getNetworkMetrics(serverId: string, hours = 24): Promise<NetworkMetrics> {
    // Netcup API returns: { "timestamp": { "rx_bytes": value, "tx_bytes": value }, ... }
    const rawData = await this.apiRequest<Record<string, Record<string, number>>>(
      `/api/v1/servers/${serverId}/metrics/network?hours=${hours}`
    )

    const rxBps: MetricsDataPoint[] = []
    const txBps: MetricsDataPoint[] = []

    if (rawData && typeof rawData === 'object' && !Array.isArray(rawData)) {
      const timestamps = Object.keys(rawData).sort()

      for (const timestamp of timestamps) {
        const netValues = rawData[timestamp]
        if (netValues && typeof netValues === 'object') {
          // Netcup returns: { "<mac> RX": value, "<mac> TX": value }
          // Find keys containing "RX" or "TX"
          let rxValue = 0
          let txValue = 0

          for (const [key, val] of Object.entries(netValues)) {
            if (key.includes('RX') && typeof val === 'number') {
              rxValue += val
            }
            if (key.includes('TX') && typeof val === 'number') {
              txValue += val
            }
          }

          rxBps.push({ timestamp, value: Math.round(rxValue * 100) / 100 })
          txBps.push({ timestamp, value: Math.round(txValue * 100) / 100 })
        }
      }
    }

    return { rxBps, txBps }
  }

  /**
   * Get all metrics for a server
   */
  async getAllMetrics(serverId: string, hours = 24): Promise<ServerMetrics> {
    const [cpu, disk, network] = await Promise.all([
      this.getCpuMetrics(serverId, hours),
      this.getDiskMetrics(serverId, hours),
      this.getNetworkMetrics(serverId, hours),
    ])

    return {
      cpu,
      disk,
      network,
      period: `${hours}h`,
    }
  }

  // ========== Snapshots ==========

  /**
   * List all snapshots for a server
   */
  async getSnapshots(serverId: string): Promise<Snapshot[]> {
    const data = await this.apiRequest<Snapshot[] | { snapshots: Snapshot[] }>(
      `/api/v1/servers/${serverId}/snapshots`
    )
    // Handle both array and wrapped response
    return Array.isArray(data) ? data : (data.snapshots || [])
  }

  /**
   * Create a new snapshot
   */
  async createSnapshot(serverId: string, name?: string): Promise<SnapshotCreateResult> {
    const body = name ? { name } : {}
    const result = await this.apiRequest<SnapshotCreateResult>(
      `/api/v1/servers/${serverId}/snapshots`,
      {
        method: 'POST',
        body: JSON.stringify(body),
      }
    )
    return result
  }

  /**
   * Check if snapshot creation is possible (dry run)
   */
  async canCreateSnapshot(serverId: string): Promise<{ possible: boolean; reason?: string }> {
    try {
      const result = await this.apiRequest<{ possible: boolean; reason?: string }>(
        `/api/v1/servers/${serverId}/snapshots:dryrun`,
        { method: 'POST' }
      )
      return result
    } catch {
      return { possible: false, reason: 'Unable to check snapshot availability' }
    }
  }

  /**
   * Delete a snapshot
   */
  async deleteSnapshot(serverId: string, snapshotName: string): Promise<void> {
    await this.apiRequest(`/api/v1/servers/${serverId}/snapshots/${encodeURIComponent(snapshotName)}`, {
      method: 'DELETE',
    })
  }

  /**
   * Revert to a snapshot
   */
  async revertSnapshot(serverId: string, snapshotName: string): Promise<TaskResponse> {
    return this.apiRequest<TaskResponse>(
      `/api/v1/servers/${serverId}/snapshots/${encodeURIComponent(snapshotName)}/revert`,
      { method: 'POST' }
    )
  }

  // ========== Server Linking ==========

  /**
   * Link a Netcup server to an order by IP
   * Returns the server ID if found
   */
  async linkServerByIp(ip: string): Promise<string | null> {
    try {
      const server = await this.findServerByIp(ip)
      return server?.id || null
    } catch {
      return null
    }
  }

  /**
   * Set hostname for a server (used at provisioning start)
   * Format: {customer}-{shortOrderId}
   */
  async setServerHostname(
    serverId: string,
    customer: string,
    orderId: string
  ): Promise<void> {
    const shortId = orderId.substring(0, 8)
    const hostname = `${customer}-${shortId}`
    await this.updateHostname(serverId, hostname)
  }
}

// Export singleton instance
export const netcupService = NetcupService.getInstance()
