<?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\utils\ReverseDNSHelper;

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

    CONST KEY_NAMESPACE = "MGModule\DNSManager2\mgLibs\custom\dns\dnssec\\";
    CONST ZSKID = 1;
    CONST KSKID = 2;


    public $configFields = [
        'username' => [
            'friendlyName' => 'Username',
            'validators'   => [
                'required' => 'required',
            ]
        ],
        'password' => [
            'friendlyName' => 'Password',
            'type'         => 'password',
            'validators'   => [
                'required' => 'required',
            ]
        ],
        'hostname' => [
            'friendlyName' => 'Hostname',
            'validators'   => [
                'required' => 'required',
            ]
        ],
        'ssl'      => [
            'friendlyName' => 'SSL',
            'type'         => 'yesno',
            'validators'   => [
                'required' => 'required',
            ]
        ],
        'port'     => [
            'friendlyName' => 'Port',
            'type'         => 'number',
            'value'        => '8053',
            'validators'   => [
                'required' => 'required',
            ]
        ],
        'httpauth' => array(
            'friendlyName' => 'HTTP Auth Type',
            'type'         => 'select',
            'options'      => array(CURLAUTH_BASIC => 'BASIC', CURLAUTH_DIGEST => 'DIGEST'),
        ),
        'ttl'      => [
            'friendlyName' => 'TTL',
            'type'         => 'number',
            'value'        => '360',
            'validators'   => [
                'min'      => 1,
                'required' => 'required',
            ]
        ],
    ];

    private $primaryDNS     = '';
    private $defaultTTL     = '';
    public  $availableTypes = [
        'A',
        'AAAA',
        'NS',
        'MX',
        'CNAME',
        'TXT',
        'SRV',
        'PTR',
        'DNSKEY'
    ];
    /**
     * @var \MGModule\DNSManager2\mgLibs\custom\dns\submodules\SimpleDNSV8\Api
     */
    private $api;


    public function loadApiInstance()
    {
        if (!$this->api)
        {
            $params    = [
                'login'    => $this->config['username'],
                'password' => $this->config['password'],
                'hostname' => $this->config['hostname'],
                'port'     => $this->config['port'],
                'ssl'      => $this->config['ssl'],
                'httpauth' => $this->config['httpauth']
            ];
            $curl      = new SimpleDNSV8\Curl($params);
            $this->api = new SimpleDNSV8\Api($curl);

            return $this->api;
        }

        return $this->api;
    }

    public function testConnection()
    {
        try
        {
            $this->loadApiInstance();
            $this->api->getZone('mgdomain.com');
        }
        catch (\Exception $ex)
        {
            if (strpos(strtolower($ex->getMessage()), 'not found') !== false || strpos(strtoupper($ex->getMessage()), 'IN SOA') !== false)
            {
                return true;
            }
            if (strlen($ex->getMessage()) === 0)
            {
                throw new exceptions\DNSSubmoduleException('Error: Invalid login details', dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
            }
            throw new exceptions\DNSSubmoduleException('Error: ' . $ex->getMessage(), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
        }

        return true;
    }

    public function getZones()
    {
        $this->loadApiInstance();

        $zones = $this->api->getZones();
        $out   = [];
        foreach ($zones as $zone)
        {
            $out[trim($zone->Name)] = '';
        }
        return $out;
    }

    public function zoneExists()
    {
        try
        {
            $this->loadApiInstance();
            $zones = $this->api->getZones();

            foreach ($zones as $zone)
            {
                if (trim(strtolower($zone->Name)) == strtolower($this->domain))
                {
                    return true;
                }
            }
            return false;
        }
        catch (\Exception $e)
        {
            if ($e->getCode() == dns\SubmoduleExceptionCodes::COMMAND_ERROR)
            {
                return false;
            }
            throw new exceptions\DNSSubmoduleException($e->getMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
    }

    public function getRecords($recordType = false)
    {
        $availableRecords = $recordType !== false ? array(strtoupper($recordType)) : $this->getAvailableRecordTypes();
         
        try
        {
            $this->loadApiInstance();
            $response = $this->api->getZoneRecords($this->domain);
            $records  = [];

            foreach ($response as $line => $each)
            {
                if(!in_array(strtoupper($each->Type), $availableRecords))
                {
                    continue;
                }
                $record       = new dns\record\Record();
                $record->name = $each->Name;
                $record->ttl  = empty($each->TTL) ? $this->defaultTTL : $each->TTL;
                $record->type = $each->Type;
                $record->line = $line;
                $record->createRDATAObject();
                $record->rdata->fromString(trim($each->Data));
                $record->customData = $each->Comment;
                $this->deleteDot($record);
                $records[] = $record;
            }
            return $records;
        }
        catch (\Exception $ex)
        {
            throw new exceptions\DNSSubmoduleException($ex->getMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
    }

    public function addRecord(dns\record\Record $record)
    {

        try
        {
            $this->loadApiInstance();
            $this->api->addRecord($this->domain, $this->recordToParamsArray($record));
        }
        catch (\Exception $ex)
        {
            throw new exceptions\DNSSubmoduleException($ex->getMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
    }

    public function editRecord(dns\record\Record $record)
    {
        try
        {
            $recordsList = $this->getRecords();
            foreach ($recordsList as $rec)
            {
                if ($record->line == $rec->line)
                {
                    $this->deleteRecord($rec);
                    $this->addRecord($record);
                    break;
                }
            }
        }
        catch (\Exception $ex)
        {
            throw new exceptions\DNSSubmoduleException($ex->getMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
    }

    public function deleteRecord(dns\record\Record $record)
    {
        try
        {
            $this->loadApiInstance();
            if (substr($record->name, -1) == '.')
            {
                $record->name = substr($record->name, 0, -1);
            }
            $this->addDot($record);
            $data = [
                'Name' => $record->name,
                'Type' => $record->type,
                'Data' => $record->rdata->toString()
            ];
            $this->api->removeRecord($this->domain, $data);
        }
        catch (\Exception $ex)
        {
            throw new exceptions\DNSSubmoduleException($ex->getMessage(), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
    }

    public function activateZone()
    {

        $this->loadApiInstance();
        $data = [
            'Type'       => "Primary",
            'DefaultTTL' => (int)$this->config['ttl'],
        ];

        $this->api->createZone($this->domain, $data);

        if (!$this->zoneExists())
        {
            throw new exceptions\DNSSubmoduleException('Cannot create zone', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
    }

    public function terminateZone()
    {
        $this->loadApiInstance();
        $this->api->deleteZone($this->domain);

        if ($this->zoneExists())
        {
            throw new exceptions\DNSSubmoduleException('Cannot terminate zone', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
    }

    private function addDot(dns\record\Record &$record)
    {
        if ($record->type == 'CNAME' && $record->rdata->toString() == '@')
        {
            return;
        }

        if ($record->type == 'SRV' || $record->type == 'MX' || $record->type == 'NS' || $record->type == 'CNAME' || $record->type == 'PTR')
        {
            $str = $record->rdata->toString();
            $str = rtrim($str, '.') . '.';
            $record->rdata->fromString($str);
        }
    }

    private function deleteDot(dns\record\Record &$record)
    {
        if ($record->type == 'SRV' || $record->type == 'MX' || $record->type == 'NS' || $record->type == 'CNAME' || $record->type == 'PTR')
        {
            $string = rtrim($record->rdata->toString(), '.');
            $record->rdata->fromString($string);
        }
    }

    public function getRDNSRecord( $ip )
    {
        $clone = $this->getClone($ip);
        $reverseRecordName =  ReverseDNSHelper::reverseRecordName($ip);

        foreach( $clone->getRecords('PTR') as $record )
        {
            $record->name = rtrim($record->name, '.') . '.';
            if( $record->name === $reverseRecordName || $record->nameToRelative($clone->domain) === $reverseRecordName )
            {
                return $record;
            }
        }
        return false;
    }

    private function recordToParamsArray(dns\record\Record $record)
    {
        $relativeName = $record->nameToRelative($this->domain);
        $this->addDot($record);
        if (!$relativeName || $relativeName == '@')
        {
            $relativeName = $this->domain;
        }
        else if (strpos($relativeName, $this->domain) === false)
        {
            if (substr($relativeName, -1) !== '.')
            {
                $relativeName .= '.' . $this->domain;
            }
            else
            {
                $relativeName .= $this->domain;
            }
        }

        return [
            'Name' => $relativeName,
            'Type' => $record->type,
            'TTL'  => (int)$record->ttl,
            'Data' => $record->rdata->toString(),
        ];
    }

    public function getSignKeys()
    {

        $this->loadApiInstance();

        $result = $this->api->getDNSSecKeys($this->domain);


        $dnssec  = new \MGModule\DNSManager2\mgLibs\custom\dns\dnssec\DnsSec();
        $dnsKeys = $this->getRecords('DNSKEY');
        $dnsKeys = $this->convertToIdAsKey($dnsKeys);


        foreach ($result as $DNSSECkey)
        {
            $class = SELF::KEY_NAMESPACE . $DNSSECkey->Type;
            if (!class_exists($class))
            {
                continue;
            }
            $key = new $class();
            $key->setId($DNSSECkey->ID);
            $key->setStarted($DNSSECkey->Created);
            $key->setBits($DNSSECkey->KeySize);
            $key->setDnsKey($dnsKeys[$DNSSECkey->ID]);
            $dnssec->addKey($key);
        }

        return $dnssec;


    }

    public function convertToIdAsKey($dnsKeys)
    {
        $out = [];
        foreach ($dnsKeys as $dnsKey)
        {
            $id = $this->findKeyId($dnsKey->customData);
            if(!$id)
            {
                continue;
            }
            $out[$id] = $dnsKey->rdata;
        }
        return $out;
    }

    public function findKeyId($string)
    {
        $result = explode('/', $string);
        foreach ($result as $piece)
        {
            if(stripos('Key:', $piece))
            {
                continue;
            }
            return preg_replace('/[^0-9]/', '', $piece);
        }
        return false;
    }

    public function sign()
    {
        $this->loadApiInstance();
        $data = [
            'Type'      => (new dns\dnssec\ZSK())->getType(),
            'Algorithm' => dns\dnssec\Algorithms::getAlgorithm(15),
            'KeySize'   => 2048,
        ];
        $this->api->createOrUpdateDNSSECkey($this->domain, SELF::ZSKID, $data);

        $data['Type'] = (new dns\dnssec\KSK())->getType();
        $this->api->createOrUpdateDNSSECkey($this->domain, SELF::KSKID, $data);

        $this->api->dnsSecSign($this->domain, 365);
    }

    public function unsign()
    {
        $this->loadApiInstance();
        $this->api->deleteDNSSECkey($this->domain, SELF::ZSKID);
        $this->api->deleteDNSSECkey($this->domain, SELF::KSKID);
    }

    public function rectify()
    {
        $this->loadApiInstance();
        $this->unsign();
        $this->sign();
    }

    public function isSigned()
    {
        $this->loadApiInstance();
        $result = $this->api->getDNSSecKeys($this->domain);
        if (empty($result))
        {
            return false;
        }
        return true;
    }

}