<?php

namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules;

use \MGModule\DNSManager2\mgLibs\custom\dns;
use \MGModule\DNSManager2\mgLibs\custom\dns\exceptions;
use \MGModule\DNSManager2\mgLibs\custom\dns\interfaces;
use \MGModule\DNSManager2\mgLibs\custom\dns\utils\Patterns;
use \MGModule\DNSManager2\mgLibs\custom\dns\submodules\cPanel as cPanelUapi;
use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;

class CPanel extends dns\SubmoduleAbstract implements 
        interfaces\SubmoduleIPInterface, 
        interfaces\SubmoduleRDNSInterface, 
        interfaces\SubmoduleTTLInterface,
        interfaces\SubmoduleDNSSecInterface,
        interfaces\SubmoduleImportInterface
{
    public $configFields = array(
        'username' =>array (
                'friendlyName'  =>  'Username',
                'validators' => array(
                    'required' => 'required',
                )
        ),
        'password' =>array (
                'friendlyName'  => 'User Password',
                'type'=> 'password',
        ),
        'resellerAccount' =>array (
                'friendlyName'  =>  'Reseller Account',
                'type'=> 'yesno',
        ),
        'hash' =>array (
                'friendlyName'  => 'Access Key',
                'type'=> 'textarea',
        ),
        'hostname' =>array (
                'friendlyName'  =>  'Hostname/IP',
                'validators' => array(
                    'required' => 'required',
                )
        ),
        'ssl' =>array (
                'friendlyName'  =>  'Enable SSL',
                'type'=> 'yesno',
        ),
        'timeout' =>array (
                'friendlyName'  =>  'Timeout',
                'help' => 'Set timeout in second for the API connection. If value is empty, the default value is 30',
        ),
        'default_ip' =>array (
                'friendlyName'  =>  'Default IP',  
                'validators' => array(
                    'required' => 'required',
                    'pattern' => Patterns::IP4_OR_IP6,
                )
        ),
        'allow_create' =>array (
                'friendlyName'  =>  'In cPanel Zones',    
                'type' => 'yesno',
                'help' => 'Allow to create zones when already in CPanel as account',
        ),
        'template' =>array (
                'friendlyName'  =>  'Template',
                'help' => 'Default zone template from your cPanel'
        ),
    );
    
    public $availableTypes = array('A', 'AAAA','CAA', 'NS', 'MX', 'CNAME', 'DNAME', 'LOC', 'TXT', 'SRV', 'PTR', 'AFSDB', 'NAPTR', 'RP', 'SOA');

    protected $uapi;

    public function testConnection()
    {
        try
        {
            $this->get('get_nameserver_config');
            return true;
        }
        catch( exceptions\DNSSubmoduleException $e )
        {
            $this->get('listzones', ['api.chunk.enable' => 1, 'api.chunk.size' => 1]);
            return true;
        }
    }

    public function getRecords($recordType = false) {
        $out = $this->get('dumpzone', array(
            'domain' => $this->domain
        ));

        $return = array();
        if(!isset($out->zone[0]->record)){
            throw new exceptions\DNSSubmoduleException('Unable to fetch records from server', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
        foreach($out->zone[0]->record as $r) {
            $recordAdapter = '\MGModule\DNSManager2\mgLibs\custom\dns\submodules\cPanel\\'.$r->type.'Adapter';
            if (class_exists($recordAdapter))
            {
                $return[] = $recordAdapter::toDnsManagerRecord((array)$r);
            }
            elseif ( in_array((string)$r->type, $recordType !== false ? [strtoupper($recordType)] : $this->getAvailableRecordTypes()) )
            {
                $return[] = dns\record\Record::tryToCreateFromArray((array)$r);
            }
        }

        return $return;
    }

    public function addRecord(dns\record\Record $record)
    {
        $adapterClassName = '\MGModule\DNSManager2\mgLibs\custom\dns\submodules\cPanel\\'.$record->type.'Adapter';

        if(class_exists($adapterClassName))
        {
            /** @var dns\record\Record $record */
            $record = new $adapterClassName($record);
        }
        $input = $record->toMergedArray(false);

        $input['domain'] = $this->domain;
        $input['name'] = IdnaHelper::idnaEncode($record->nameToAbsolute($this->domain));
        $this->logAction('addRecord', $input, $record);
        $this->get('addzonerecord', $input); 
        return true;
    }
    
    public function editRecord(dns\record\Record $record) {
        $records = $this->getRecords();
        if(empty($records))
            return false;
        
        $lines = array();
        foreach($records as $r) {
            $lines[$r['line']] = $r;
        }

        if(!isset($lines[$record['line']]) || !$record->isEqualTo($lines[$record['line']])) {
            $adapterClassName = '\MGModule\DNSManager2\mgLibs\custom\dns\submodules\cPanel\\'.$record->type.'Adapter';
            if(class_exists($adapterClassName))
            {
                /** @var dns\record\Record $record */
                $record = new $adapterClassName($record);
            }
            $input = $record->toMergedArray(false);
            $input['domain'] = $this->domain;
            $input['name'] = IdnaHelper::idnaEncode($record->nameToAbsolute($this->domain));

            $this->get('editzonerecord', $input);
        }
        
        
    }
    
    public function deleteRecord(dns\record\Record $record) {
        $this->get('removezonerecord', array(
            'zone' => $this->domain,
            'line' => $record['line']
        ));
    }

    public function zoneExists()
    {
        try
        {
            $this->get('dumpzone', ['domain' => $this->domain]);
        }
        catch( exceptions\DNSSubmoduleException $e )
        {
            if( $e->getCode() == dns\SubmoduleExceptionCodes::COMMAND_ERROR )
            {
                return false;
            }
            throw $e;
        }
        return true;
    }
    
    public function activateZone() {
        if($this->ip != '') {
            if(!filter_var($this->ip, FILTER_VALIDATE_IP)) {
                throw new exceptions\DNSSubmoduleException('IP is not valid!', dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
            }
        } else {
            $this->ip = $this->config['default_ip'];
        }
        $params = array(
            'domain' => $this->domain,
            'ip' => $this->ip            
        );

        if($this->config['resellerAccount'] == "on"){
            $params['trueowner'] = $this->config['username'];
        }
        if($this->config['template'] != "")
            $params['template'] = $this->config['template']; 
        
        try {
            $this->get('adddns', $params);
        } catch (exceptions\DNSSubmoduleException $e) {
            if(preg_match('/is owned by another user/', $e->getMessage()) && $this->config['allow_create'] == 'on') {
                $res = $this->get('listaccts', array('want' => 'user', 'searchtype' => 'domain', 'search' => $this->domain, 'searchmethod' => 'exact'));
                $user = $res->acct[0]->user;
                $params['trueowner'] = $user;
                $this->get('adddns', $params);
                return ;
            }
            throw $e;
        }
    }
   
    public function terminateZone() {
        $this->get('killdns', array(
            'domain' => $this->domain
        ));
    }
    
    public function getZones()
    {
        $out = array();
        
        $res = $this->get('listzones', array());

        foreach($res->zone as $zone)
        {
            $out[(string)$zone->domain] = '';
        }

        //TODO: tu skonczyłem - dokończyc whm1
        
        try
        {
            $res = $this->get('listaccts', array('want' => 'domain,ip'));
            
            foreach ($res->acct as $zone)
            {
                if(isset($out[(string)$zone->domain]))
                {
                    $out[(string)$zone->domain] = (string)$zone->ip;
                }
            }
        }
        catch(exceptions\DNSSubmoduleException $exc)
        {
            
        }

        return $out;
    }
    
    private function get($function, $params=array()) {
        $params['api.version'] = 1;
        $url = ($this->config['ssl']? 'https://'.$this->config['hostname'].':2087' : 'http://'.$this->config['hostname'].':2086').'/json-api/'.$function;

        $ch = curl_init(); 
        if(is_array($params)) {
            $url .= '?';
            foreach($params as $key=>$value) {
                $value = urlencode($value);
                $url .= "{$key}={$value}&";
            }
        }
        $url = trim($url, '&');
        $this->log('REQUEST: ' . $url);
        $chOptions = array (
                CURLOPT_URL => $url,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_SSL_VERIFYHOST => false,
                CURLOPT_TIMEOUT => is_numeric($this->config['timeout']) ? intval($this->config['timeout']) : 30
        );
        
        if($this->config['hash'] != '') { 
            $header[0] = 'Authorization: WHM '.$this->config['username'].':'.str_replace(array("\r", "\n"),"", $this->config['hash']);
            $chOptions[CURLOPT_HTTPHEADER] = $header;
        } elseif($this->config['password'] != '') { 
            $chOptions[CURLOPT_USERPWD] = $this->config['username'].':'.$this->config['password'];
        } else {
            throw new exceptions\DNSSubmoduleException('Password or Access key is required', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
        }
 
        curl_setopt_array($ch, $chOptions);
        $out = curl_exec($ch);

        $this->log('RESPONSE: ' . $out);
        if (curl_errno($ch)) {
            throw new exceptions\DNSSubmoduleException("cURL Error: " . curl_errno($ch) . " - " . curl_error($ch), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
        }
        
        $out_info = curl_getinfo($ch);
        if($out_info['http_code'] != 200) {
            if($out_info['http_code'] == 301 || $out_info['http_code'] == 302){
                throw new exceptions\DNSSubmoduleException('Module require SSL', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
            }
        }
        
        curl_close($ch);
        
        if(strpos($out, 'SSL encryption is required for access to this server') !== FALSE) {
            throw new exceptions\DNSSubmoduleException('SSL encryption is required for access to this server', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
        }

        $a = json_decode($out);
        if($a===FALSE) {
            throw new exceptions\DNSSubmoduleException('Unable to parse response', dns\SubmoduleExceptionCodes::INVALID_RESPONSE);
        }

        if ($a->cpanelresult->error && $a->cpanelresult->data->result == 0)
        {
            throw new exceptions\DNSSubmoduleException($a->cpanelresult->error, dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }

        if(isset($a->status) && $a->status == 0){ 
             throw new exceptions\DNSSubmoduleException($a->statusmsg?:'Unknown Error', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
        
        if(isset($a->data->result) && $a->data->result == 0) {
            throw new exceptions\DNSSubmoduleException($a->data->reason?:'Unknown Error', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
        
        if(!empty($a->error)) {
            throw new exceptions\DNSSubmoduleException($a->error, dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
        
        if(isset($a->metadata->result) && $a->metadata->result == 0) {
            throw new exceptions\DNSSubmoduleException($a->metadata->reason, dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
        
//        if(!isset($a->data)) {
//            throw new exceptions\DNSSubmoduleException("There is no data in output", dns\SubmoduleExceptionCodes::INVALID_RESPONSE);
//        }
        return isset($a->data)?$a->data:$a;
    }
    
    private function logAction($action, $request, $response)
    {
        $addonConfig = \MGModule\DNSManager2\addon::config();   
        logmodulecall(
            $addonConfig['name'], 
            'cPanel '.($action), 
            array($request),
            array($response),
            null,
            array($this->config['hostname'], $this->config['hostname'], $this->config['hash'])
        ); 
    }

    public function getSignKeys(){
        $fetchDSRecords = $this->get('fetch_ds_records_for_domains', array('domain' => $this->domain));
        $dnssec   = new \MGModule\DNSManager2\mgLibs\custom\dns\dnssec\DnsSec();

        foreach($fetchDSRecords->domains[0]->ds_records->keys as $dsKey => $dsRecord){
            foreach($dsRecord->digests as $diggestKey => $diggestRecord){
                $ds = new dns\record\type\DS();
                $ds->setKeytag($dsRecord->key_tag);
                $ds->setAlgorithm($dsRecord->algo_num);
                $ds->setDigestType($diggestRecord->algo_num);
                $ds->setDigest($diggestRecord->digest);
                $dnssec->addDs($ds);
            }

            switch (strtolower($dsRecord->key_type)){
                case 'csk':
                    $dnskey = new dns\record\type\DNSKEY();
                    $dnskey->setFlags($dsRecord->flags);
                    $dnskey->setProtocol(3);
                    $dnskey->setAlgorithm($dsRecord->algo_num);

                    $zoneKey = new dns\dnssec\CSK();
                    $zoneKey->setId($dsKey);
                    $zoneKey->setBits($dsRecord->bits);
                    $zoneKey->setDnsKey($dnskey);

                    $dnssec->addKey($zoneKey);
                    break;
                case 'ksk':
                    $dnskey = new dns\record\type\DNSKEY();
                    $dnskey->setFlags($dsRecord->flags);
                    $dnskey->setProtocol(3);
                    $dnskey->setAlgorithm($dsRecord->algo_num);

                    $ksk = new dns\dnssec\KSK();
                    $ksk->setId($dsKey);
                    $ksk->setBits($dsRecord->bits);
                    $ksk->setDnsKey($dnskey);

                    $dnssec->addKey($ksk);
                    break;
                case 'zsk':
                    $dnskey = new dns\record\type\DNSKEY();
                    $dnskey->setFlags($dsRecord->flags);
                    $dnskey->setProtocol(3);
                    $dnskey->setAlgorithm($dsRecord->algo_num);

                    $zsk = new dns\dnssec\ZSK();
                    $zsk->setId($dsKey);
                    $zsk->setBits($dsRecord->bits);
                    $zsk->setDnsKey($dnskey);

                    $dnssec->addKey($zsk);
                    break;
                }
        }

        return $dnssec;

    }

    public function sign(){
       $this->get('enable_dnssec_for_domains', array('domain' => $this->domain));
    }

    public function unsign(){
        $this->get('disable_dnssec_for_domains', array('domain' => $this->domain));
    }

    public function rectify(){
        return true;
    }

    public function isSigned(){
        $fetchDSRecords = $this->get('fetch_ds_records_for_domains', array('domain' => $this->domain));
        $checkDsRecords = $fetchDSRecords->domains[0]->ds_records;

        if(empty(get_object_vars($checkDsRecords)))
            return false;

        return true;
    }

/* No need to use it anymore. It is not deleted to have it in future implementations - just in case */
/*    private function uAPI()
    {
        $res = $this->get('listaccts', array('want' => 'user', 'searchtype' => 'domain', 'search' => $this->domain, 'searchmethod' => 'exact'));
        $user = $res->acct[0]->user;

        if (!is_null($this->uapi))
        {
            return $this->uapi;
        }
        $this->uapi = new cPanelUapi\Uapi();
        $this->uapi->setLogin($this->config, $user)
            ->createSession();

        return $this->uapi;
    }
*/


}
