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

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

interface CloudflareZone {
  id: string
  name: string
  status: string
  created_on?: string
  modified_on?: string
  account?: {
    id?: string
  }
  name_servers?: string[]
  plan?: {
    name?: string
  }
  [key: string]: unknown
}

interface CloudflareTunnel {
  id: string
  name: string
  status: string
  created_at?: string | number
  connections?: unknown[]
  [key: string]: unknown
}

interface CloudflareAPIResponse<T> {
  result: T[]
  success: boolean
  errors?: unknown[]
  [key: string]: unknown
}

export class CloudflareAdapter implements InfrastructureAdapter {
  readonly provider: ResourceProvider = 'CLOUDFLARE'
  
  private apiToken: string
  private accountId: string

  constructor(config: { apiToken: string; accountId: string }) {
    this.apiToken = config.apiToken
    this.accountId = config.accountId
  }

  async discoverResources(): Promise<NormalizedResource[]> {
    const resources: NormalizedResource[] = []

    try {
      // Discover Cloudflare Tunnels
      const tunnels = await this.getTunnels()
      for (const tunnel of tunnels) {
        resources.push(this.normalizeTunnel(tunnel))
      }

      // Discover DNS zones
      const zones = await this.getZones()
      for (const zone of zones) {
        resources.push(this.normalizeZone(zone))
      }
    } catch (error) {
      logger.error('Error discovering Cloudflare resources', { error })
      throw error
    }

    return resources
  }

  private async getTunnels(): Promise<CloudflareTunnel[]> {
    const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/cfd_tunnel`, {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${this.apiToken}`,
        'Content-Type': 'application/json',
      },
    })

    if (!response.ok) {
      throw new Error(`Cloudflare API error: ${response.status} ${response.statusText}`)
    }

    const data = (await response.json()) as CloudflareAPIResponse<CloudflareTunnel>
    return data.result || []
  }

  private async getZones(): Promise<CloudflareZone[]> {
    const response = await fetch('https://api.cloudflare.com/client/v4/zones', {
      method: 'GET',
      headers: {
        'Authorization': `Bearer ${this.apiToken}`,
        'Content-Type': 'application/json',
      },
    })

    if (!response.ok) {
      throw new Error(`Cloudflare API error: ${response.status} ${response.statusText}`)
    }

    const data = (await response.json()) as CloudflareAPIResponse<CloudflareZone>
    return data.result || []
  }

  private normalizeTunnel(tunnel: CloudflareTunnel): NormalizedResource {
    return {
      id: `cloudflare-tunnel-${tunnel.id}`,
      name: tunnel.name || `Tunnel ${tunnel.id}`,
      type: 'tunnel',
      provider: 'CLOUDFLARE',
      providerId: tunnel.id,
      providerResourceId: `cloudflare://tunnels/${tunnel.id}`,
      status: tunnel.status || 'active',
      metadata: {
        accountId: this.accountId,
        createdAt: tunnel.created_at,
        connections: tunnel.connections || [],
      },
      tags: [],
      createdAt: tunnel.created_at ? new Date(tunnel.created_at) : new Date(),
      updatedAt: new Date(),
    }
  }

  private normalizeZone(zone: CloudflareZone): NormalizedResource {
    return {
      id: `cloudflare-zone-${zone.id}`,
      name: zone.name,
      type: 'dns_zone',
      provider: 'CLOUDFLARE',
      providerId: zone.id,
      providerResourceId: `cloudflare://zones/${zone.id}`,
      status: zone.status,
      metadata: {
        accountId: zone.account?.id,
        nameServers: zone.name_servers || [],
        plan: zone.plan?.name,
      },
      tags: [],
      createdAt: zone.created_on ? new Date(zone.created_on) : new Date(),
      updatedAt: zone.modified_on ? new Date(zone.modified_on) : new Date(),
    }
  }

  async getResource(providerId: string): Promise<NormalizedResource | null> {
    try {
      // Try to get as tunnel first
      try {
        const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/cfd_tunnel/${providerId}`, {
          method: 'GET',
          headers: {
            'Authorization': `Bearer ${this.apiToken}`,
            'Content-Type': 'application/json',
          },
        })

        if (response.ok) {
          const data = (await response.json()) as CloudflareAPIResponse<CloudflareTunnel>
          if (data.result && data.result.length > 0) {
            return this.normalizeTunnel(data.result[0])
          }
        }
      } catch (error) {
        // Not a tunnel, try zone
        try {
          const response = await fetch(`https://api.cloudflare.com/client/v4/zones/${providerId}`, {
            method: 'GET',
            headers: {
              'Authorization': `Bearer ${this.apiToken}`,
              'Content-Type': 'application/json',
            },
          })

          if (response.ok) {
            const data = (await response.json()) as CloudflareAPIResponse<CloudflareZone>
            if (data.result && data.result.length > 0) {
              return this.normalizeZone(data.result[0])
            }
          }
        } catch (error) {
          return null
        }
      }

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

  async createResource(spec: ResourceSpec): Promise<NormalizedResource> {
    try {
      if (spec.type === 'tunnel') {
        // Create Cloudflare Tunnel
        const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/cfd_tunnel`, {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${this.apiToken}`,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            name: spec.name,
            config_src: spec.config.config_src || 'local',
          }),
        })

        if (!response.ok) {
          const error = (await response.json()) as { errors?: Array<{ message?: string }> }
          throw new Error(`Failed to create tunnel: ${error.errors?.[0]?.message || response.statusText}`)
        }

        const data = (await response.json()) as CloudflareAPIResponse<CloudflareTunnel>
        if (!data.result || data.result.length === 0) {
          throw new Error('No tunnel result returned from API')
        }
        return this.normalizeTunnel(data.result[0])
      } else if (spec.type === 'dns_zone') {
        // Create DNS Zone
        const response = await fetch('https://api.cloudflare.com/client/v4/zones', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${this.apiToken}`,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            name: spec.name,
            account: {
              id: this.accountId,
            },
            ...spec.config,
          }),
        })

        if (!response.ok) {
          const error = (await response.json()) as { errors?: Array<{ message?: string }> }
          throw new Error(`Failed to create zone: ${error.errors?.[0]?.message || response.statusText}`)
        }

        const data = (await response.json()) as CloudflareAPIResponse<CloudflareZone>
        if (!data.result || data.result.length === 0) {
          throw new Error('No zone result returned from API')
        }
        return this.normalizeZone(data.result[0])
      } else {
        throw new Error(`Unsupported resource type: ${spec.type}`)
      }
    } catch (error) {
      logger.error('Error creating Cloudflare resource', { error })
      throw error
    }
  }

  async updateResource(providerId: string, spec: Partial<ResourceSpec>): Promise<NormalizedResource> {
    try {
      // Try to get the resource first to determine its type
      const existing = await this.getResource(providerId)
      if (!existing) {
        throw new Error(`Resource ${providerId} not found`)
      }

      if (existing.type === 'tunnel') {
        // Update Cloudflare Tunnel
        const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/cfd_tunnel/${providerId}`, {
          method: 'PATCH',
          headers: {
            'Authorization': `Bearer ${this.apiToken}`,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            name: spec.name || existing.name,
            ...spec.config,
          }),
        })

        if (!response.ok) {
          const error = (await response.json()) as { errors?: Array<{ message?: string }> }
          throw new Error(`Failed to update tunnel: ${error.errors?.[0]?.message || response.statusText}`)
        }

        const data = (await response.json()) as CloudflareAPIResponse<CloudflareTunnel>
        if (!data.result || data.result.length === 0) {
          throw new Error('No tunnel result returned from API')
        }
        return this.normalizeTunnel(data.result[0])
      } else if (existing.type === 'dns_zone') {
        // Update DNS Zone
        const response = await fetch(`https://api.cloudflare.com/client/v4/zones/${providerId}`, {
          method: 'PATCH',
          headers: {
            'Authorization': `Bearer ${this.apiToken}`,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            ...spec.config,
          }),
        })

        if (!response.ok) {
          const error = (await response.json()) as { errors?: Array<{ message?: string }> }
          throw new Error(`Failed to update zone: ${error.errors?.[0]?.message || response.statusText}`)
        }

        const data = (await response.json()) as CloudflareAPIResponse<CloudflareZone>
        if (!data.result || data.result.length === 0) {
          throw new Error('No zone result returned from API')
        }
        return this.normalizeZone(data.result[0])
      } else {
        throw new Error(`Unsupported resource type: ${existing.type}`)
      }
    } catch (error) {
      logger.error(`Error updating Cloudflare resource ${providerId}`, { error, providerId })
      throw error
    }
  }

  async deleteResource(providerId: string): Promise<boolean> {
    try {
      // Try to get the resource first to determine its type
      const existing = await this.getResource(providerId)
      if (!existing) {
        return false
      }

      if (existing.type === 'tunnel') {
        // Delete Cloudflare Tunnel
        const response = await fetch(`https://api.cloudflare.com/client/v4/accounts/${this.accountId}/cfd_tunnel/${providerId}`, {
          method: 'DELETE',
          headers: {
            'Authorization': `Bearer ${this.apiToken}`,
            'Content-Type': 'application/json',
          },
        })

        return response.ok
      } else if (existing.type === 'dns_zone') {
        // Delete DNS Zone
        const response = await fetch(`https://api.cloudflare.com/client/v4/zones/${providerId}`, {
          method: 'DELETE',
          headers: {
            'Authorization': `Bearer ${this.apiToken}`,
            'Content-Type': 'application/json',
          },
        })

        return response.ok
      } else {
        return false
      }
    } catch (error) {
      logger.error(`Error deleting Cloudflare resource ${providerId}`, { error, providerId })
      return false
    }
  }

  async getMetrics(providerId: string, timeRange: TimeRange): Promise<NormalizedMetrics[]> {
    try {
      const metrics: NormalizedMetrics[] = []
      
      // Get analytics from Cloudflare Analytics API
      // For zones, use zone analytics
      try {
        const response = await fetch(
          `https://api.cloudflare.com/client/v4/zones/${providerId}/analytics/dashboard?since=${timeRange.start.toISOString()}&until=${timeRange.end.toISOString()}`,
          {
            method: 'GET',
            headers: {
              'Authorization': `Bearer ${this.apiToken}`,
              'Content-Type': 'application/json',
            },
          }
        )

        if (response.ok) {
          const data = (await response.json()) as { result?: { totals?: { requests?: { all?: number, cached?: number }, bandwidth?: { all?: number, cached?: number } } } }
          const result = data.result

          // Network throughput
          if (result?.totals?.requests?.all) {
            metrics.push({
              resourceId: providerId,
              metricType: 'REQUEST_RATE',
              value: result.totals.requests.all,
              timestamp: new Date(),
              labels: { type: 'all' },
            })
          }

          // Bandwidth
          if (result?.totals?.bandwidth?.all) {
            metrics.push({
              resourceId: providerId,
              metricType: 'NETWORK_THROUGHPUT',
              value: result.totals.bandwidth.all,
              timestamp: new Date(),
              labels: { type: 'all' },
            })
          }

          // Error rate
          if (result?.totals?.requests?.cached && result?.totals?.requests?.all) {
            const errorRate = ((result.totals.requests.all - result.totals.requests.cached) / result.totals.requests.all) * 100
            metrics.push({
              resourceId: providerId,
              metricType: 'ERROR_RATE',
              value: errorRate,
              timestamp: new Date(),
              labels: { type: 'calculated' },
            })
          }
        }
      } catch (error) {
        // Not a zone, try tunnel metrics
        try {
          const response = await fetch(
            `https://api.cloudflare.com/client/v4/accounts/${this.accountId}/cfd_tunnel/${providerId}/connections`,
            {
              method: 'GET',
              headers: {
                'Authorization': `Bearer ${this.apiToken}`,
                'Content-Type': 'application/json',
              },
            }
          )

          if (response.ok) {
            const data = (await response.json()) as { result?: unknown[] }
            const connections = data.result || []

            metrics.push({
              resourceId: providerId,
              metricType: 'NETWORK_THROUGHPUT',
              value: connections.length,
              timestamp: new Date(),
              labels: { type: 'connections' },
            })
          }
        } catch (tunnelError) {
          // Metrics not available for this resource type
          logger.warn(`Metrics not available for resource ${providerId}`, { providerId })
        }
      }

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

  async getRelationships(providerId: string): Promise<NormalizedRelationship[]> {
    try {
      const relationships: NormalizedRelationship[] = []

      // Try to get as tunnel first
      try {
        const tunnelResponse = await fetch(
          `https://api.cloudflare.com/client/v4/accounts/${this.accountId}/cfd_tunnel/${providerId}`,
          {
            method: 'GET',
            headers: {
              'Authorization': `Bearer ${this.apiToken}`,
              'Content-Type': 'application/json',
            },
          }
        )

        if (tunnelResponse.ok) {
          // Get DNS routes for this tunnel
          try {
            const routesResponse = await fetch(
              `https://api.cloudflare.com/client/v4/accounts/${this.accountId}/cfd_tunnel/${providerId}/routes`,
              {
                method: 'GET',
                headers: {
                  'Authorization': `Bearer ${this.apiToken}`,
                  'Content-Type': 'application/json',
                },
              }
            )

            if (routesResponse.ok) {
              const routesData = (await routesResponse.json()) as { result?: Array<{ zone_id?: string, hostname?: string, path?: string }> }
              const routes = routesData.result || []

              for (const route of routes) {
                if (route.zone_id) {
                  relationships.push({
                    sourceId: providerId,
                    targetId: route.zone_id,
                    type: 'routes_to',
                    metadata: {
                      hostname: route.hostname,
                      path: route.path,
                    },
                  })
                }
              }
            }
          } catch (error) {
            // Routes not available
          }
        }
      } catch (error) {
        // Not a tunnel, try zone relationships
        try {
          // Get DNS records for this zone
          const dnsResponse = await fetch(
            `https://api.cloudflare.com/client/v4/zones/${providerId}/dns_records`,
            {
              method: 'GET',
              headers: {
                'Authorization': `Bearer ${this.apiToken}`,
                'Content-Type': 'application/json',
              },
            }
          )

          if (dnsResponse.ok) {
            const dnsData = (await dnsResponse.json()) as { result?: Array<{ id?: string, type?: string, name?: string, content?: string }> }
            const records = dnsData.result || []

            for (const record of records) {
              if (record.id) {
                relationships.push({
                  sourceId: providerId,
                  targetId: record.id,
                  type: 'contains',
                  metadata: {
                    type: record.type,
                    name: record.name,
                    content: record.content,
                  },
                })
              }
            }
          }

          // Get tunnel connections to this zone
          const tunnels = await this.getTunnels()
          for (const tunnel of tunnels) {
            try {
              const routesResponse = await fetch(
                `https://api.cloudflare.com/client/v4/accounts/${this.accountId}/cfd_tunnel/${tunnel.id}/routes`,
                {
                  method: 'GET',
                  headers: {
                    'Authorization': `Bearer ${this.apiToken}`,
                    'Content-Type': 'application/json',
                  },
                }
              )

              if (routesResponse.ok) {
                const routesData = (await routesResponse.json()) as { result?: Array<{ zone_id?: string, hostname?: string }> }
                const routes = routesData.result || []

                for (const route of routes) {
                  if (route.zone_id === providerId) {
                    relationships.push({
                      sourceId: tunnel.id,
                      targetId: providerId,
                      type: 'routes_to',
                      metadata: {
                        hostname: route.hostname,
                      },
                    })
                  }
                }
              }
            } catch (error) {
              // Skip this tunnel
            }
          }
        } catch (error) {
          // Relationships not available
        }
      }

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

  async healthCheck(): Promise<HealthStatus> {
    try {
      const response = await fetch('https://api.cloudflare.com/client/v4/user/tokens/verify', {
        method: 'GET',
        headers: {
          'Authorization': `Bearer ${this.apiToken}`,
          '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(),
      }
    }
  }
}

