<?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;

class DNSMadeEasy extends dns\SubmoduleAbstract implements interfaces\SubmoduleTTLInterface, interfaces\SubmoduleImportInterface, interfaces\SubmoduleRDNSInterface
{

    public $configFields = [
        'apikey'     => [
            'friendlyName' => 'API Key',
            'validators'   => [
                'required' => 'required',
            ]
        ],
        'api_secret' => [
            'friendlyName' => 'API Secret',
            'type'         => 'password',
            'validators'   => [
                'required' => 'required',
            ]
        ],
        'test'       => [
            'friendlyName' => 'Test Mode',
            'type'         => 'yesno',
        ],
    ];

    public $availableTypes = ['A', 'AAAA', 'ANAME', 'NS', 'MX', 'CNAME', 'SPF', 'SRV', 'TXT', 'PTR','HTTPRED'];

    private $domain_id;

    const API_BASE_URL      = 'http://api.dnsmadeeasy.com/V2.0/';
    const API_BASE_URL_TEST = 'http://api.sandbox.dnsmadeeasy.com/V2.0/';


    public function testConnection()
    {
        $info = $this->_get('dns/managed/');

        if( !isset($info['data']) )
        {
            throw new exceptions\DNSSubmoduleException('Unknown Error', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
        }
    }

    public function zoneExists()
    {
        if( $this->domain_id() > 0 )
        {
            $result = $this->_get('dns/managed/' . $this->domain_id());
            if( $result['id'] > 0 )
            {
                return true;
            }
        }
        return false;
    }

    public function getRecords( $recordType = false )
    {
        $result = $this->_get('dns/managed/' . $this->domain_id() . '/records');

        $out = [];
        foreach( $result['data'] as $data )
        {
            if( in_array((string)$data['type'], $recordType ? [strtoupper($recordType)] : $this->getAvailableRecordTypes(), true) )
            {
                $record       = new dns\record\Record();
                $record->line = (string)$data['id'];
                $record->name = (string)$data['name'];
                $record->type = (string)$data['type'];
                $record->ttl  = (string)$data['ttl'];
                $record->createRDATAObject();

                switch( (string)$data['type'] )
                {
                    case 'MX':
                        $record->rdata->preference = (string)$data['mxLevel'];
                        $record->rdata->exchange   = (string)$data['value'];
                        break;
                    case 'HTTPRED':
                        $record->rdata->link         = $data['value'];
                        $record->rdata->title        = $data['title'];
                        $record->rdata->keywords     = $data['keywords'];
                        $record->rdata->redirecttype = $data['redirectType'];
                        $record->rdata->hardlink     = (int)$data['hardLink'];
                        break;
                    case 'SRV':
                        $record->rdata->setDataFromArray($data);
                        $record->rdata->target = (string)$data['value'];
                        break;
                    default:
                        $record->rdata->setFirstProperty((string)$data['value']);
                        break;
                }

                $out[] = $record;
            }
        }

        return $out;
    }

    private function recordToParamsArray( dns\record\Record $record )
    {
        $params = [
            'name' => str_replace('@', '', $record->nameToRelative($this->domain)),
            'ttl'  => $record->ttl,
            'type' => $record->type
        ];
        switch( $record->type )
        {
            case 'PTR':
                $params['value'] = trim($record->rdata->ptrdname, '.') . '.';
                break;
            case 'MX':
                $params['mxLevel'] = $record->rdata->preference;
                $params['value']   = $this->makeAbsolute($record->rdata->exchange);
                break;
            case 'HTTPRED':
                $params['title']        = $record->rdata->title;
                $params['keywords']     = $record->rdata->keywords;
                $params['redirectType'] = $record->rdata->redirecttype;
                $params['hardLink']     = (bool)$record->rdata->hardlink;
                $params['value']        = $record->rdata->link;
                break;
            case 'SRV':
                $params = array_merge($params, $record->rdata->toArray(false));
                unset($params['target']);
                $params['value'] = $record->rdata->target;
                $params['ttl'] = $record->ttl;
                $params['type'] = $record->type;
                break;
            case 'NS':
                $params['value'] = $this->makeAbsolute($record->rdata->nsdname);
                break;
            case 'ANAME':
                $params['value'] = $this->makeAbsolute($record->rdata->aname);
                break;
            case 'CNAME':
                $params['value'] = $this->makeAbsolute($record->rdata->cname);
                break;
            default:
                $params['value'] = $record->rdata->toString();
                break;
        }

        return $params;
    }

    public function addRecord( dns\record\Record $record )
    {
        $params = $this->recordToParamsArray($record);
        $this->_post('dns/managed/' . $this->domain_id() . '/records/ ', $params);
    }

    public function editRecord( dns\record\Record $record )
    {
        $params                = $this->recordToParamsArray($record);
        $params['gtdLocation'] = "DEFAULT";
        $params['id']          = $record->line;
        $this->_put('dns/managed/' . $this->domain_id() . '/records/' . $record->line . '/', $params);
    }

    public function deleteRecord( dns\record\Record $record )
    {
        $this->_delete('dns/managed/' . $this->domain_id() . '/records/' . $record->line);
    }

    public function activateZone()
    {
        $input = [
            'names' => [$this->domain],
        ];
        $this->_post('dns/managed', $input);
    }

    public function terminateZone()
    {
        $this->_delete('dns/managed', [$this->domain_id()]);
    }

    private function _get( $operation )
    {
        return $this->_curl($operation, 'GET');
    }

    private function _delete( $operation, $data = [] )
    {
        return $this->_curl($operation, 'DELETE', $data);
    }

    private function _put( $operation, $data = [] )
    {
        return $this->_curl($operation, 'PUT', $data);
    }

    private function _post( $operation, $data = [] )
    {
        return $this->_curl($operation, 'POST', $data);
    }

    private function _curl( $operation, $method, $postData = null )
    {
        $url = ($this->config['test'] == 'on' ? self::API_BASE_URL_TEST : self::API_BASE_URL) . $operation;

        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        //curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, '_headerCallback'));
        curl_setopt($ch, CURLOPT_HTTPHEADER, $this->_requestHeaders());

        if( $method === 'POST' || $method === 'DELETE' )
        {
            foreach( $postData as $key => $value )
            {
                if( in_array($key, ['PRIORITY', 'WEIGHT', 'PORT', 'TARGET'], true) )
                {
                    if( $key === 'TARGET' )
                    {
                        unset($postData['TARGET']);
                    }
                    else
                    {
                        $postData[strtolower($key)] = $value;
                    }
                    unset($postData[$key]);
                }
            }
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData));
        }
        elseif( $method === 'PUT' )
        {
            curl_setopt($ch, CURLOPT_HTTPHEADER,  // Ask the remote end to PUT, curl won't PUT a raw body
                array_merge(['X-HTTP-Method-Override: PUT'], $this->_requestHeaders()));
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData));
        }
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);

        // reset the header values
        $this->_headers = [];

        $apiResponse = curl_exec($ch);

        if( curl_errno($ch) )
        {
            throw new exceptions\DNSSubmoduleException("cURL Error: " . curl_errno($ch) . " - " . curl_error($ch), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
        }

        if( empty($apiResponse) )
        {
            $apiResponse = '[]';
        }
        $apiResponse = preg_replace('/([{,]+)(\s*)([^"]+?)\s*:/', '$1"$3":', $apiResponse); //key => "key"
        $result      = json_decode($apiResponse, true);

        if( !is_array($result) )
        {
            throw new exceptions\DNSSubmoduleException('Unable to parse response (' . $apiResponse . ')', dns\SubmoduleExceptionCodes::INVALID_RESPONSE);
        }

        $ci = curl_getinfo($ch);

        if( !empty($result['error']) || strpos($ci['http_code'], '2') !== 0 )
        {
            throw new exceptions\DNSSubmoduleException($result['error'][0] ? : 'Unknown Error', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
        return $result;
    }

    private function _requestHeaders()
    {
        $requestDate = gmdate('r');
        return [
            "x-dnsme-apiKey: {$this->config['apikey']}",
            "x-dnsme-requestDate: $requestDate",
            "x-dnsme-hmac: " . $this->_hash($requestDate),
            'content-type:application/json',
            'accept:application/json',
        ];
    }

    private function _hash( $requestDate )
    {
        return hash_hmac('sha1', $requestDate, $this->config['api_secret']);
    }

    private function domain_id()
    {
        if( isset($this->domain_id) )
        {
            return $this->domain_id;
        }

        $info = $this->_get('dns/managed/');
        foreach( $info['data'] as $value )
        {
            if( $value['name'] === $this->domain )
            {
                $this->domain_id = $value['id'];
                return $value['id'];
            }
        }
    }

    public function getZones()
    {
        $out  = [];
        $info = $this->_get('dns/managed/');
        foreach( $info['data'] as $value )
        {
            $out[$value['name']] = '';
        }
        return $out;
    }

    /**
     * @param $name
     *
     * @return string
     */
    private function makeAbsolute( $name )
    {
        return rtrim($name, '. ') . '.';
    }
}
