<?php
/**
 * PowerDNS API Client
 * 
 * This class provides a wrapper for the PowerDNS API to manage DNS records
 * through the Web Hosting Panel interface.
 * 
 * @author Web Hosting Panel
 */
class PowerDNSAPI {
    private $apiUrl;
    private $apiKey;
    private $serverID;
    private $lastError = null;
    
    /**
     * Constructor
     * 
     * @param string $apiUrl The base URL for the PowerDNS API (e.g., 'http://localhost:8081/api/v1')
     * @param string $apiKey The API key for authentication
     * @param string $serverID The server ID (usually 'localhost')
     */
    public function __construct($apiUrl, $apiKey, $serverID = 'localhost') {
        $this->apiUrl = rtrim($apiUrl, '/');
        $this->apiKey = $apiKey;
        $this->serverID = $serverID;
    }
    
    /**
     * Get the last error message
     * 
     * @return string|null The last error message or null if no error occurred
     */
    public function getLastError() {
        return $this->lastError;
    }
    
    /**
     * Make an API request
     * 
     * @param string $endpoint The API endpoint (e.g., '/servers/localhost/zones')
     * @param string $method The HTTP method (GET, POST, PUT, DELETE)
     * @param array $data The data to send (for POST, PUT)
     * @return array|false The response data as an associative array or false if an error occurred
     */
    private function apiRequest($endpoint, $method = 'GET', $data = null) {
        $url = $this->apiUrl . $endpoint;
        
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'X-API-Key: ' . $this->apiKey,
            'Content-Type: application/json',
            'Accept: application/json'
        ]);
        
        if ($data && ($method === 'POST' || $method === 'PUT' || $method === 'PATCH')) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        }
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        
        if (curl_errno($ch)) {
            $this->lastError = 'cURL Error: ' . curl_error($ch);
            curl_close($ch);
            return false;
        }
        
        curl_close($ch);
        
        if ($httpCode >= 400) {
            $this->lastError = 'API Error: HTTP Code ' . $httpCode . ' - ' . $response;
            return false;
        }
        
        // Return empty array for 204 No Content responses
        if ($httpCode == 204) {
            return [];
        }
        
        $responseData = json_decode($response, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            $this->lastError = 'JSON Error: ' . json_last_error_msg();
            return false;
        }
        
        return $responseData;
    }
    
    /**
     * Get all zones
     * 
     * @return array|false List of zones or false if an error occurred
     */
    public function getZones() {
        return $this->apiRequest("/servers/{$this->serverID}/zones");
    }
    
    /**
     * Get a specific zone
     * 
     * @param string $zoneName The zone name (e.g., 'example.com')
     * @return array|false Zone details or false if an error occurred
     */
    public function getZone($zoneName) {
        return $this->apiRequest("/servers/{$this->serverID}/zones/{$zoneName}");
    }
    
    /**
     * Create a new zone
     * 
     * @param string $zoneName The zone name (e.g., 'example.com')
     * @param array $nameservers List of nameservers
     * @param string $kind Type of zone (Native, Master, Slave)
     * @param array $options Additional zone options
     * @return array|false The created zone or false if an error occurred
     */
    public function createZone($zoneName, $nameservers = [], $kind = 'Native', $options = []) {
        $data = [
            'name' => $zoneName,
            'kind' => $kind,
            'nameservers' => $nameservers
        ];
        
        // Merge additional options
        if (!empty($options)) {
            $data = array_merge($data, $options);
        }
        
        return $this->apiRequest("/servers/{$this->serverID}/zones", 'POST', $data);
    }
    
    /**
     * Delete a zone
     * 
     * @param string $zoneName The zone name to delete
     * @return bool True if successful, false otherwise
     */
    public function deleteZone($zoneName) {
        $result = $this->apiRequest("/servers/{$this->serverID}/zones/{$zoneName}", 'DELETE');
        return $result !== false;
    }
    
    /**
     * Get records for a zone
     * 
     * @param string $zoneName The zone name
     * @return array|false List of records or false if an error occurred
     */
    public function getRecords($zoneName) {
        $zone = $this->getZone($zoneName);
        if ($zone === false) {
            return false;
        }
        
        return isset($zone['rrsets']) ? $zone['rrsets'] : [];
    }
    
    /**
     * Add a record to a zone
     * 
     * @param string $zoneName The zone name
     * @param string $name The record name (e.g., 'www.example.com')
     * @param string $type The record type (A, AAAA, CNAME, MX, TXT, etc.)
     * @param int $ttl TTL in seconds
     * @param array $records Array of content strings for the records
     * @return bool True if successful, false otherwise
     */
    public function addRecord($zoneName, $name, $type, $ttl = 3600, $records = []) {
        $data = [
            'rrsets' => [
                [
                    'name' => $name,
                    'type' => $type,
                    'ttl' => $ttl,
                    'changetype' => 'REPLACE',
                    'records' => array_map(function($content) {
                        return [
                            'content' => $content,
                            'disabled' => false
                        ];
                    }, $records)
                ]
            ]
        ];
        
        $result = $this->apiRequest("/servers/{$this->serverID}/zones/{$zoneName}", 'PATCH', $data);
        return $result !== false;
    }
    
    /**
     * Add multiple records to a zone in a single API call
     * 
     * @param string $zoneName The zone name
     * @param string $name The record name (e.g., 'www.example.com')
     * @param string $type The record type (A, AAAA, CNAME, MX, TXT, etc.)
     * @param int $ttl TTL in seconds
     * @param array $records Array of content strings for the records
     * @return bool True if successful, false otherwise
     */
    public function addMultipleRecords($zoneName, $name, $type, $ttl = 3600, $records = []) {
        // This is an alias for addRecord since it already supports multiple records
        return $this->addRecord($zoneName, $name, $type, $ttl, $records);
    }
    
    /**
     * Update a record in a zone
     * 
     * @param string $zoneName The zone name
     * @param string $name The record name
     * @param string $type The record type
     * @param int $ttl TTL in seconds
     * @param array $records Array of content strings for the records
     * @return bool True if successful, false otherwise
     */
    public function updateRecord($zoneName, $name, $type, $ttl = 3600, $records = []) {
        // The PowerDNS API uses the same approach for adding and updating records
        return $this->addRecord($zoneName, $name, $type, $ttl, $records);
    }
    
    /**
     * Delete a record from a zone
     * 
     * @param string $zoneName The zone name
     * @param string $name The record name
     * @param string $type The record type
     * @return bool True if successful, false otherwise
     */
    public function deleteRecord($zoneName, $name, $type) {
        $data = [
            'rrsets' => [
                [
                    'name' => $name,
                    'type' => $type,
                    'changetype' => 'DELETE'
                ]
            ]
        ];
        
        $result = $this->apiRequest("/servers/{$this->serverID}/zones/{$zoneName}", 'PATCH', $data);
        return $result !== false;
    }
    
    /**
     * Get DNSSEC status for a zone
     * 
     * @param string $zoneName The zone name
     * @return array|false DNSSEC information or false if an error occurred
     */
    public function getDNSSEC($zoneName) {
        return $this->apiRequest("/servers/{$this->serverID}/zones/{$zoneName}/cryptokeys");
    }
    
    /**
     * Enable DNSSEC for a zone
     * 
     * @param string $zoneName The zone name
     * @return bool True if successful, false otherwise
     */
    public function enableDNSSEC($zoneName) {
        // First check if DNSSEC keys exist
        $keys = $this->getDNSSEC($zoneName);
        if ($keys === false) {
            return false;
        }
        
        // If no keys exist, create them
        if (empty($keys)) {
            // Create KSK (Key Signing Key)
            $ksk = $this->apiRequest("/servers/{$this->serverID}/zones/{$zoneName}/cryptokeys", 'POST', [
                'kind' => 'ksk',
                'active' => true
            ]);
            
            if ($ksk === false) {
                return false;
            }
            
            // Create ZSK (Zone Signing Key)
            $zsk = $this->apiRequest("/servers/{$this->serverID}/zones/{$zoneName}/cryptokeys", 'POST', [
                'kind' => 'zsk',
                'active' => true
            ]);
            
            return $zsk !== false;
        }
        
        // If keys exist but are not active, activate them
        $allSuccess = true;
        foreach ($keys as $key) {
            if (!$key['active']) {
                $result = $this->apiRequest(
                    "/servers/{$this->serverID}/zones/{$zoneName}/cryptokeys/{$key['id']}", 
                    'PUT', 
                    ['active' => true]
                );
                if ($result === false) {
                    $allSuccess = false;
                }
            }
        }
        
        return $allSuccess;
    }
    
    /**
     * Disable DNSSEC for a zone
     * 
     * @param string $zoneName The zone name
     * @return bool True if successful, false otherwise
     */
    public function disableDNSSEC($zoneName) {
        $keys = $this->getDNSSEC($zoneName);
        if ($keys === false) {
            return false;
        }
        
        $allSuccess = true;
        foreach ($keys as $key) {
            if ($key['active']) {
                $result = $this->apiRequest(
                    "/servers/{$this->serverID}/zones/{$zoneName}/cryptokeys/{$key['id']}", 
                    'PUT', 
                    ['active' => false]
                );
                if ($result === false) {
                    $allSuccess = false;
                }
            }
        }
        
        return $allSuccess;
    }
    
    /**
     * Search for records matching a given pattern
     * 
     * @param string $zoneName The zone name
     * @param string $query The search query
     * @param int $max Maximum number of results
     * @return array|false Search results or false if an error occurred
     */
    public function searchRecords($zoneName, $query, $max = 100) {
        return $this->apiRequest("/servers/{$this->serverID}/zones/{$zoneName}/search-data?q=" . urlencode($query) . "&max=" . $max);
    }
    
    /**
     * Export a zone to BIND zone file format
     * 
     * @param string $zoneName The zone name
     * @return string|false Zone file content or false if an error occurred
     */
    public function exportZone($zoneName) {
        $result = $this->apiRequest("/servers/{$this->serverID}/zones/{$zoneName}/export");
        return $result !== false ? $result['zone'] : false;
    }
    
    /**
     * Import a zone from BIND zone file format
     * 
     * @param string $zoneName The zone name
     * @param string $zoneContent The zone file content
     * @return bool True if successful, false otherwise
     */
    public function importZone($zoneName, $zoneContent) {
        $data = [
            'zone' => $zoneContent
        ];
        
        $result = $this->apiRequest("/servers/{$this->serverID}/zones/{$zoneName}/import", 'POST', $data);
        return $result !== false;
    }
}