<?php

namespace MGModule\DNSManager2\mgLibs\custom\dns;

use InvalidArgumentException;
use MGModule\DNSManager2 as main;
use MGModule\DNSManager2\mgLibs\custom\dns\utils\IDNAConvert;
use MGModule\DNSManager2\mgLibs\custom\dns\utils\ReverseDNSHelper;
use MGModule\DNSManager2\mgLibs\custom\manager\GlobalSettingHelper;
use MGModule\DNSManager2\models\custom\globalsetting\GlobalSettingEnum;
use MGModule\DNSManager2\models\custom\server\Server;

//TODO: optymalizacja dla multiple delete?
abstract class SubmoduleAbstract implements interfaces\SubmoduleConfigurationInterface, interfaces\SubmoduleInterface
{
    protected $hasTTL = false;
    protected $supportRDNS = false;
    protected $requireIP = false;
    protected $supportImport = false;
    protected $supportDNSSEC = false;
    protected $isRemoveableDefaultServerRecordsSet = false;

    protected $availableTypes = [];
    protected $configFields = [];

    protected $domain = false;
    protected $config;

    protected $debug = false;

    protected $ip = '';

    /**
     * @var Server
     */
    protected $server = false;

    /**
     * @var main\mgLibs\custom\helpers\ZoneLogger\Manager
     */
    protected $zoneLoggerManager;

    public function __construct()
    {
        $this->supportRDNS                         = $this instanceof interfaces\SubmoduleRDNSInterface;
        $this->requireIP                           = $this instanceof interfaces\SubmoduleIPInterface;
        $this->hasTTL                              = $this instanceof interfaces\SubmoduleTTLInterface;
        $this->supportImport                       = $this instanceof interfaces\SubmoduleImportInterface;
        $this->supportDNSSEC                       = $this instanceof interfaces\SubmoduleDNSSecInterface;
        $this->isRemoveableDefaultServerRecordsSet = $this instanceof interfaces\SubmoduleRemoveDefaultRecords;
    }

    /**
     * @return Server
     */
    public function getServer()
    {
        return $this->server;
    }

    /**
     * @param Server $server
     */
    public function setServer(Server $server)
    {
        $this->server = $server;
    }

    public function getNameServers($index = false)
    {
        $nameservers = $this->server->getNameservers($index);
        $out         = [];
        foreach ($nameservers as $nameserver)
        {
            if ('' != $nameserver->name)
            {
                $out[] = $nameserver->name;
            }
        }
        return $out;
    }

    public function setDomain($domain)
    {
        $idna         = new IDNAConvert();
        $this->domain = $idna->encode($domain);
    }

    //TODO: podstawowe parsowanie błędóww!

    //TODO: sprawdzic specjalne znaki kodowane przez WHMCSa
    public function setConfiguration($config)
    {
        if (isset($config['password']))
        {
            $config['password'] = html_entity_decode($config['password']);
        }

        $this->config = $config;
    }

    public function getConfiguration()
    {
        return $this->configFields;
    }

    public function getAvailableRecordTypes()
    {
        $array = $this->availableTypes;
        sort($array);
        return $array;
    }

    public function isIPRequired()
    {
        return $this->requireIP;
    }

    public function isRDNSSupported()
    {
        return $this->supportRDNS;
    }

    public function isTTLEnabled()
    {
        return $this->hasTTL;
    }

    public function isImportSupported()
    {
        return $this->supportImport;
    }

    /**
     * Return true if DNSSEC is supported
     * @return bool
     */
    public function isDNSSECSupported()
    {
        return $this->supportDNSSEC;
    }


    public function enableDebug()
    {
        $this->debug = true;
    }

    public function disableDebug()
    {
        $this->debug = false;
    }

    protected function log($message, $type = 'INFO')
    { //TODO: dodać interfejs + domyślna klasa logera
        if (!$this->debug)
        {
            return;
        }

        $class = explode('\\', get_class($this));
        $path  = __DIR__ . DIRECTORY_SEPARATOR . 'submodules' . DIRECTORY_SEPARATOR . 'logs' . DIRECTORY_SEPARATOR . array_pop($class);
        if (!file_exists($path))
        {
            @mkdir($path, 0777, true);
        }
        $filepath = $path . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log';
        $line     = date('Y-m-d H:i:s') . "\t" . $type . "\t" . $message . "\r\n";
        @file_put_contents($filepath, $line, FILE_APPEND);
    }

    public function hasRecordSupport($recordType)
    {
        if (in_array($recordType, $this->availableTypes))
        {
            return true;
        }

        return false;
    }

    public function isDisabledPopulateNs()
    {
        return $this->disabledPopulateNs;
    }

//===========================================================================================================
//===========================================STANDARD rDNS===================================================
//===========================================================================================================
    protected function getClone($ip)
    {
        $clone = clone $this;
        $clone->setDomain(ReverseDNSHelper::reverseZoneName($ip));
        return $clone;
    }

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

        foreach ($clone->getRecords('PTR') as $record)
        {
            if ($record->name == ReverseDNSHelper::reverseRecordName($ip)
                || $record->nameToRelative($clone->domain) == ReverseDNSHelper::reverseRecordName($ip))
            {

                return $record;
            }
        }
        return false;
    }

    public function getRDNSRecordsNumber($ip)
    {
        $clone  = $this->getClone($ip);
        $result = $clone->getRecords('PTR');
        return count($result);
    }

    public function updateRDNS($ip, $ttl = false, $value = false)
    {
        if (!$this->isRDNSSupported())
        {
            throw new exceptions\DNSSubmoduleException('rDNS is not supported', SubmoduleExceptionCodes::COMMAND_ERROR);
        }

        $clone = $this->getClone($ip);
        if (!$clone->zoneExists())
        {
            $clone->activateZone();
            $this->addNSRecordsRDNS($clone);
        }
        $record = $this->getRDNSRecord($ip);
        if ($record === false)
        {
            $record = ReverseDNSHelper::createPTRRecord($ip, $ttl, $value ?: $this->domain);
            $clone->addRecord($record);
            return;
        }

        $record->rdata->setFirstProperty($value);
        $clone->editRecord($record);
    }

    public function addNSRecordsRDNS($clone)
    {
        $zone = (new main\models\custom\zone\Repository())->byName($this->domain)->one();
        if ($zone)
        {
            $defaultSet = $zone->getPackage()->getDefaultSet();
        }

        // if default record set is set for package
        if ($defaultSet)
        {
            $records = $defaultSet->getRecords();
            foreach ($records as $r)
            {
                if ($r->type === 'NS')
                {
                    $record       = new record\Record();
                    $record->name = $r->name;
                    $record->type = $r->type;
                    $record->ttl  = $r->ttl;
                    $record->createRDATAObject('NS');
                    $record->rdata->setFirstProperty($r->rdata['nsdname']);
                    $clone->addRecord($record);
                    $this->getLoggerManager()->logAddRecordToZone($zone, $record);
                }
            }
        }
    }

    public function removeRDNS($ip)
    {

        if (!$this->isRDNSSupported())
        {
            throw new exceptions\DNSSubmoduleException('rDNS is not supported', SubmoduleExceptionCodes::COMMAND_ERROR);
        }

        $clone = $this->getClone($ip);

        if (!$clone->zoneExists())
        {
            return;
        }

        $record = $this->getRDNSRecord($ip);

        if ($record)
        {
            $clone->deleteRecord($record);
        }

        $rdnsNumber = $this->getRDNSRecordsNumber($ip);
        if ($rdnsNumber < 1 && GlobalSettingHelper::getSetting(GlobalSettingEnum::DO_NOT_REMOVE_EMPTY_PTR_ZONES) !== 'on') // 1 cause w just deleted one record
        {
            $clone->terminateZone();
        }
    }

    public function wipeRecords()
    {
        $records      = $this->getRecords();
        $cannotDelete = [];
        foreach ($records as &$record)
        {
            if ($record->type === 'SOA')
            {
                continue;
            }
            $recordName = $record->name;
            try
            {
                $this->deleteRecord($record);
                $records = $this->getRecords();
            }
            catch (\Exception $ex)
            {
                $record->name   = $recordName;
                $cannotDelete[] = $record;
                continue;
            }
        }

        return $cannotDelete;
    }

    public function wipeRecordsArray(array $wipe)
    {
        $records = $this->getRecords();
        foreach ($wipe as $r)
        {
            foreach ($records as &$record)
            {
                if ($r->name == $record->name && $r->type == $record->type && $r->rdata == $record->rdata)
                {
                    try
                    {
                        $r->line = $record->line;
                        $this->deleteRecord($r);
                        $records = $this->getRecords();
                    }
                    catch (\Exception $ex)
                    {
                        continue;
                    }
                }
            }
        }
        return;
    }
//===========================================================================================================
//===========================================IP DEFAULT======================================================
//===========================================================================================================
    public function setIP($ip)
    {
        $this->ip = $ip;
    }

    public function removeDefaultServerRecordsSet($defaultModuleRecords)
    {
        if ($this->isRemoveableDefaultServerRecordsSet)
        {
            $this->removeDefaultServerRecords($defaultModuleRecords);
        }
    }

    public function isRemoveableDefaultServerRecordsSet()
    {
        return $this->isRemoveableDefaultServerRecordsSet;
    }

    public function hasCustomEditRecords()
    {
        return $this instanceof interfaces\SubmoduleCustomEditRecords;
    }

    public function getLoggerManager()
    {
        if (!$this->zoneLoggerManager)
        {
            $this->zoneLoggerManager = new main\mgLibs\custom\helpers\ZoneLogger\Manager($_SESSION['uid']);
        }
        return $this->zoneLoggerManager;
    }

    public function getDomain()
    {
        return $this->domain;
    }

    public function validateRecord($record)
    {
        foreach ($this->validators() as $validatorName => $validatingFunction)
        {
            if (!$validatingFunction($record->{$validatorName}, $record->type))
            {
                $langName     = strtolower($record->type) . ucfirst($validatorName) . "ValidationError";
                $errorMessage = main\mgLibs\lang::absoluteT('addonAA', $langName);
                $errorMessage = (!$errorMessage || $langName == $errorMessage)
                    ? main\mgLibs\lang::absoluteT('addonAA', 'defaultNameValidationError')
                    : $errorMessage;

                throw new InvalidArgumentException($errorMessage);
            }
        }

        $record->rdata->validate();
    }

    protected function validators(): array
    {
        return [
            'name' =>
                function (string $name, string $type = '')
                {
                    $idna = new IDNAConvert();
                    $name = $name == '@' ? $name : $idna->encode($name);
                    switch (strtolower($type))
                    {
                        case 'srv':
                            return preg_match("(_\w+\._\w+\.*)", $name) == 1;
                        default:
                            return preg_match("/^[@\w.-]+$/", $name) == 1;
                    }
                }
        ];
    }
}