<?php

namespace WtbFirewall\Firewall;

// Prevent direct access.
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Denne klassen brukes til å representere en firewall.
 * Den leser/skriver data i databasen, installerer og skriver firewall regler til disk.
 */
class Firewall {

    
    public function __construct() {
    
    }

    /**
     * Installer firewall i .htaccess filen.
     * Samt kopierer over firewall filer og lager firewall regel listen.
     *
     * @return bool TRUE dersom alt gikk bra. FALSE dersom firewall ikke ble installert riktig.
     */
    public function installFirewall(): bool {

        //Kopier filer for firewall.
        if (!$this->copyFirewallFile()) {
            return false;
        }

        //Sjekk om firewall skal installeres i .htaccess eller user.ini
        $method = $this->installDetectPrependMethod();

        if ($method === 'htaccess') {
            if (!$this->installUpdateHtaccess()) {
                return false;
            }
        } else if ($method === 'user.ini') {
            if (!$this->installUpdateUserIni()) {
                return false;
            }
        }

        $this->writeFirewallConfig();
        $this->writeManualRulesToFile();
        $this->writeCountryRulesToFile();
        $this->autoRulesScheduling();

        return true;
    }

    /**
     * Fjerner firewall fra .htacess filen samt sletter firewall filer.
     *
     * @return bool TRUE dersom firewall ble fjernet, FALSE hvis ikke.
     * NB! FALSE kan bety at firewall ikke var installert fra før.
     */
    public function removeFirewall(): bool {
        $files = [
            ABSPATH . '.htaccess' => ['# BEGIN WTB FIREWALL', '# END WTB FIREWALL'],
            ABSPATH . '.user.ini' => ['; BEGIN WTB FIREWALL', '; END WTB FIREWALL'],
        ];

        //Holder orden på om vi har fjernet fra en av filene.
        $removed = false;

        foreach ($files as $path => $markers) {
            if (!file_exists($path)) {
                continue;
            }

            $contents = file_get_contents($path);
            if ($contents === false) {
                continue;
            }

            list( $markerStart, $markerEnd ) = $markers;
            $pattern = '/' . preg_quote($markerStart, '/') . '.*?' . preg_quote($markerEnd, '/') . '\s*/s';
            $newContents = preg_replace($pattern, '', $contents);

            if ($newContents !== $contents) {
                file_put_contents($path, ltrim($newContents, "\r\n"));
                $removed = true;
            }
        }

        //Firewall fjernet fra .htaccess/.user.ini
        //Overskriv gammel fil med tom fil. Fjern regel filene.
        $this->writeToFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_main.php', '<?php ?>');
        $this->deleteFirewallFile();

        return $removed;
    }

    /**
     * Sjekker om firewall er installert i .htaccess filen.
     *
     * @return bool TRUE dersom installert, FALSE hvis ikke.
     * Kan også returnere FALSE dersom vi ikke kunne lese .htaccess/user.ini filen.
     */
    public function isInstalled(): bool {

        $htAccess = ABSPATH . '.htaccess';
        $userIni = ABSPATH . '.user.ini';

        if (file_exists($htAccess) && is_readable($htAccess)) {
            $contents = file_get_contents($htAccess);
            if ($contents !== false && strpos($contents, 'BEGIN WTB FIREWALL') !== false) {
                return true;
            }
        }

        if (file_exists($userIni) && is_readable($userIni)) {
            $contents = file_get_contents($userIni);
            if ($contents !== false && strpos($contents, 'BEGIN WTB FIREWALL') !== false) {
                return true;
            }
        }

        return false;
    }

    /**
     * Kopierer filen for firewall, wtb_firewall_main.php, inn i /wp-content
     *
     * @return bool TRUE dersom filen ble kopiert, FALSE dersom filen ikke ble kopiert.
     */
    private function copyFirewallFile(): bool {

        $copyFile = function (string $source, string $destination): bool {
            require_once ABSPATH . 'wp-admin/includes/file.php';

            global $wp_filesystem;

            if (!$wp_filesystem) {
                WP_Filesystem();
            }

            if (!$wp_filesystem) {
                return false;
            }

            // Check the source exists.
            if (!$wp_filesystem->exists($source)) {
                return false;
            }

            $tmp = $destination . '_tmp';

            // Copy to temp file first.
            // (Use get_contents/put_contents so this works across FS methods, not just "direct".)
            $contents = $wp_filesystem->get_contents($source);
            if (false === $contents) {
                return false;
            }

            if (!$wp_filesystem->put_contents($tmp, $contents, FS_CHMOD_FILE)) {
                return false;
            }

            // Replace destination with temp (best-effort atomic; may be copy+delete on non-direct FS).
            if (!$wp_filesystem->move($tmp, $destination, true)) { // true = overwrite
                $wp_filesystem->delete($tmp);
                return false;
            }

            // Invalidate opcache (if enabled).
            if (function_exists('opcache_invalidate')) {
                opcache_invalidate($destination, true);
            }

            // Set permissions (WP will use FS_CHMOD_FILE / direct chmod when possible).
            $wp_filesystem->chmod($destination, FS_CHMOD_FILE);

            return true;
        };

        if (!$copyFile(WTB_FIREWALL_PATH . 'wtb_firewall_main.php', WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_main.php')) {
            return false;
        }

        return true;
    }

    /**
     * Sletter filene for firewall fra /wp-content
     *  wtb_firewall_main.php, wtb_firewall_config.php
     *
     */
    private function deleteFirewallFile() {

        $deleteFile = function ($file) {
            if (file_exists($file)) {
                wp_delete_file($file);
            }
        };

        $deleteFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_config.php');
        $deleteFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_rules.php');
        $deleteFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_rules_ipv6.php');
        $deleteFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_country_rules.php');
        $deleteFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_country_rules_ipv6.php');
        $deleteFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_auto_rules.php');
        $deleteFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_auto_rules_ipv6.php');
    }

    /**
     *
     * @return string Path til php prepend script som går i .htaccess eller user.ini
     */
    private function getPrependFile(): string {

        $firewall_file = WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_main.php';

        // Normalize path (convert to absolute real path if possible)
        $firewall_path = realpath($firewall_file);
        if ($firewall_path === false) {
            // Fall back to constructed path if file doesn’t exist yet
            $firewall_path = $firewall_file;
        }

        return $firewall_path;
    }

    /**
     * Denne metoden brukes for å skrive til filer brukt av firewall.
     *
     * @param string $target Full path for filen vi skal skrive til.
     * @param type $txt Innholdet vi skal skrive.
     */
    private function writeToFile(string $target, string $txt) {
        require_once ABSPATH . 'wp-admin/includes/file.php';
        global $wp_filesystem;

        $dir = dirname($target);
        $temp = tempnam($dir, 'tmp_');

        file_put_contents($temp, $txt, LOCK_EX);

        if (!$wp_filesystem) {
            WP_Filesystem();
        }

        if ($wp_filesystem) {
            $wp_filesystem->move($temp, $target, true);
        }

        if (function_exists('opcache_invalidate')) {
            @opcache_invalidate($target, true);
        }

        if (function_exists('opcache_compile_file')) {
            @opcache_compile_file($target);
        }
    }

    /**
     * Skriver konfigurasjonen for firewall (FirewallConfig) til fil.
     *
     */
    public function writeFirewallConfig() {

        global $wpdb;

        $php = '<?php' . PHP_EOL;
        $php .= '$blockMode = ' . \WtbFirewall\Config\FirewallConfig::$blockMode . ';' . PHP_EOL;        
        $php .= '$logFile = "' . WTB_FIREWALL_UPLOAD_PATH . 'firewall_blocked.log";' . PHP_EOL;
        $php .= '$proxyServer = ' . var_export(\WtbFirewall\Config\FirewallConfig::$proxyServer, true) . ';' . PHP_EOL;

        $this->writeToFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_config.php', $php);
    }

    /**
     * Tar i mot en IPv4 addresse eller IP range. Validerer den og returnerer resultatet klart for bruk med firewall.
     *
     * Støttede formater:
     * - single IP: "192.168.0.5"
     * - CIDR: "192.168.0.0/24"
     * - range: "10.0.0.1-10.0.0.50" (godtar også en/em dash)
     *
     * @param string $input IPv4 addresse eller IP range vi skal sjekke.
     * @return Hvis feil string med feilmelding ellers følgende array {
     *   'type'      => 'single'|'cidr'|'range'
     *   'prefix'    => (int|null)   CIDR når type=cidr, 0-32
     *   'start'     => (int)        start IP lagret som int, ved cidr er dette netverket.
     *   'end'       => (int)        slutt IP lagret som int, kun brukt ved IP range.
     * }
     */
    function parseIpv4Input(string $input) {
        $input = trim((string) $input);

        if ($input === '') {
            return 'IP input is empty.';
        }

        //Gjør alle former for bindestrek (feks em dash) om til vanlig bindestrek.
        $input = str_replace(['–', '—', '−'], '-', $input);

        //Sjekk om input er IP range på formatet 192.168.0.1-192.168.1.255
        if (strpos($input, '-') !== false) {
            list( $a, $b ) = array_map('trim', explode('-', $input, 2));

            if (!\WtbFirewall\Helpers\IpHelper::isValidIpv4($a) || !\WtbFirewall\Helpers\IpHelper::isValidIpv4($b)) {
                return 'Invalid IP range.';
            }

            $ua = ip2long($a);
            $ub = ip2long($b);

            //Sørg for at IP range starter med laveste addresse først.
            if ($ua > $ub) {
                $tmp = $ua;
                $ua = $ub;
                $ub = $tmp;
            }

            return [
                'type' => 'range',
                'prefix' => null,
                'start' => $ua,
                'end' => $ub,
            ];
        }


        //Sjekk om input er IP+subnet på formatet 192.168.0.1/24
        if (strpos($input, '/') !== false) {
            list( $ip, $prefix ) = array_map('trim', explode('/', $input, 2));

            if (!ctype_digit($prefix)) {
                return 'Invalid subnet.';
            }
            $prefix = (int) $prefix;

            if ($prefix < 0 || $prefix > 32) {
                return 'CIDR prefix must be 0–32.';
            }

            if (!\WtbFirewall\Helpers\IpHelper::isValidIpv4($ip)) {
                return 'Invalid IPv4 address.';
            }

            $uip = ip2long($ip);

            // Build mask with first $prefix bits set
            $mask = $prefix === 0 ? 0 : ((0xFFFFFFFF << (32 - $prefix)) & 0xFFFFFFFF);
            $network = $uip & $mask;

            return [
                'type' => 'cidr',
                'prefix' => $prefix,
                'start' => $network,
                'end' => null,
            ];
        }


        //******************
        //Input er ikke IP range eller CIDR.
        //Da er input en enkel IP address eller ugyldig.
        if (!\WtbFirewall\Helpers\IpHelper::isValidIpv4($input)) {
            return 'Invalid IPv4 address.';
        }

        $u = ip2long($input);

        return [
            'type' => 'single',
            'prefix' => null,
            'start' => $u,
            'end' => null,
        ];
    }

    /**
     * Tar i mot en IPv6 addresse eller IPv6 CIDR range. Validerer den og returnerer resultatet klart for bruk med firewall.
     *
     * Støttede formater:
     * - single IP: "2001:db8::1"
     * - CIDR: "2001:db8::1/64"
     *
     * @param string $input IPv6 addresse vi skal sjekke.
     * @return Hvis feil string med feilmelding ellers følgende array
     *   'type'      => (string) 'single' eller 'cidr'
     *   'prefix'    => (int)   Prefix for CIDR, eksempel: 64
     *   'ipv6'     => (int)    Binary string som holder IPv6 addressen/nettverket.
     */
    public function parseIpv6Input(string $input) {

        /**
         * Tar imot en IPv6 address, binary string, og returnerer kun nettverket.
         */
        $ipv6PrefixMask = function (string $packedIpv6, int $prefix): string {
            $bytes = array_values(unpack('C*', $packedIpv6));

            $fullBytes = intdiv($prefix, 8);
            $remainBits = $prefix % 8;

            for ($i = 0; $i < 16; $i++) {
                if ($i < $fullBytes) {
                    continue;
                }

                if ($i === $fullBytes && $remainBits !== 0) {
                    $mask = (0xFF << (8 - $remainBits)) & 0xFF;
                    $bytes[$i] &= $mask;
                } else {
                    $bytes[$i] = 0;
                }
            }

            return pack('C*', ...$bytes);
        };

        $input = trim($input);
        if ($input === '') {
            return 'Invalid IPv6 address.';
        }

        // IPv6 CIDR
        if (strpos($input, '/') !== false) {
            [$ip, $prefix] = array_pad(explode('/', $input, 2), 2, '');
            $ip = trim($ip);
            $prefix = trim($prefix);

            if ($ip === '' || $prefix === '' || !ctype_digit($prefix)) {
                return 'Invalid IPv6 address.';
            }

            $prefix = (int) $prefix;
            if ($prefix < 0 || $prefix > 128) {
                return 'Invalid IPv6 address.';
            }

            if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
                return 'Invalid IPv6 address.';
            }

            $packed = inet_pton($ip);
            if ($packed === false || strlen($packed) !== 16) {
                return 'Invalid IPv6 address.';
            }

            $networkPacked = $ipv6PrefixMask($packed, $prefix);

            return [
                'type' => 'cidr',
                'prefix' => $prefix,
                'ipv6' => $networkPacked, // 16-byte binary
            ];
        }

        //Enkel IPv6 address
        if (!filter_var($input, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            return 'Invalid IPv6 address.';
        }

        $packed = inet_pton($input);
        if ($packed === false || strlen($packed) !== 16) {
            return 'Invalid IPv6 address.';
        }

        return [
            'type' => 'single',
            'prefix' => 128,
            'ipv6' => $packed, // 16-byte binary
        ];
    }

    /**
     * Validerer at oppgitt varighet for en regel er gyldig.
     * Godtar format som: 2d / 4h / 45m  (kan IKKE blande dem som 4h 20m)
     *
     * @param type $input String som oppgir varighet.
     * @return String i DATETIME format klar for lagring i database.
     *         Eller null dersom $duration hadde ugyldig format.
     */
    public function validateRuleDuration(string $input) {
        $duration = is_string($input) ? trim(strtolower($input)) : '';

        // Input må være på formatet "2d", "14h" eller "40m".
        if (!preg_match('/^(\d+)([mhd])$/', $duration, $m)) {
            return null;
        }

        $value = (int) $m[1];
        $unit = $m[2];

        //Sjekk vi ikke skal vente i negativ tid.
        if ($value <= 0) {
            return null;
        }

        //Få tak i rett tidssone.
        $site_tz = function_exists('wp_timezone') ? wp_timezone() : new \DateTimeZone('UTC');

        //Beregn tidspunktet regelen skal vare til.
        $expires = new \DateTime('now', $site_tz);
        switch ($unit) {
            case 'm': $expires->modify("+{$value} minutes");
                break;
            case 'h': $expires->modify("+{$value} hours");
                break;
            case 'd': $expires->modify("+{$value} days");
                break;
        }

        // Konvert til UTC før lagring.
        $expires->setTimezone(new \DateTimeZone('UTC'));

        return $expires->format('Y-m-d H:i:s');
    }

    /**
     * Lagrer en ny regel i databasen.
     * Det er forventet at alle parametre er allerede validert.
     *
     * @param array $ip Array med IPv4 informasjon. Samme som returnert fra parseIpv4Input()
     * @param string $ruleAction Type regel dette er allow/block.
     * @param string $ruleNote Eventuell beskrivelse/notat for regel.
     * @param int $ruleEnabled Angir om regel skal være aktivert (1) eller disabled (0).
     * @param string $dateExpire Dato og tid når regel går ut på dato. NULL dersom regel er permanent.
     * @return bool TRUE dersom regel ble lagret i databasen, FALSE hvis ikke.
     */
    public function saveIpv4Rule(array $ip, string $ruleAction, string $ruleNote, int $ruleEnabled, ?string $dateExpire): bool {

        global $wpdb;
        $table = $wpdb->prefix . 'wtb_firewall_ips';

        $nowUTC = \current_time('mysql', true);

        switch ($ip['type']) {
            case 'single':
                $ipStart = $ip['start'];
                $ipEnd = 0;
                $type = 1;
                break;
            case 'range':
                $ipStart = $ip['start'];
                $ipEnd = $ip['end'];
                $type = 2;
                break;
            case 'cidr':
                $ipStart = $ip['start'];
                $ipEnd = $ip['prefix'];
                $type = 3;
                break;
        }

        if ($ruleAction == 'allow') {
            $action = 1;
        } else if ($ruleAction == 'block') {
            $action = 2;
        }

        $data = [
            'enabled' => (int) $ruleEnabled,
            'ipv4_start' => (int) $ipStart,
            'ipv4_end' => (int) $ipEnd,
            'action' => $action,
            'type' => (int) $type,
            'source' => (int) 1,
            'description' => $ruleNote,
            'date_created' => $nowUTC,
            'date_expires' => $dateExpire,
            'date_updated' => '',
            'created_by' => \wp_get_current_user()->user_login,
        ];

        $fmt = [
            '%d', // enabled
            '%d', // ipv4_start
            '%d', // ipv4_end
            '%d', // action
            '%d', // type
            '%d', // source
            '%s', // description
            '%s', // date_created
            '%s', // date_expires
            '%s', // date_updated
            '%s', // created_by
        ];

        return ($wpdb->insert($table, $data, $fmt) !== false);
    }

    /**
     * Lagrer en ny IPv6 regel i databasen.
     * Det er forventet at alle parametre er allerede validert.
     *
     * @param array $ip Array med IPv6 informasjon. Samme som returnert fra parseIpv6Input()
     * @param string $ruleAction Type regel dette er allow/block.
     * @param string $ruleNote Eventuell beskrivelse/notat for regel.
     * @param int $ruleEnabled Angir om regel skal være aktivert (1) eller disabled (0).
     * @param string $dateExpire Dato og tid når regel går ut på dato. NULL dersom regel er permanent.
     * @return bool TRUE dersom regel ble lagret i databasen, FALSE hvis ikke.
     */
    public function saveIpv6Rule(array $ip, string $ruleAction, string $ruleNote, int $ruleEnabled, ?string $dateExpire): bool {
        global $wpdb;
        $table = $wpdb->prefix . 'wtb_firewall_ips';

        $nowUTC = \current_time('mysql', true);

        if ($ruleAction == 'allow') {
            $action = 1;
        } else if ($ruleAction == 'block') {
            $action = 2;
        }

        $data = [
            'enabled' => (int) $ruleEnabled,
            'ipv6' => $ip['ipv6'],
            'ipv6_prefix' => $ip['prefix'],
            'action' => $action,
            'type' => (int) 4,
            'source' => (int) 1,
            'description' => $ruleNote,
            'date_created' => $nowUTC,
            'date_expires' => $dateExpire,
            'date_updated' => null,
            'created_by' => \wp_get_current_user()->user_login,
        ];

        $fmt = [
            '%d', // enabled
            '%s', // ipv6
            '%d', // ipv6_prefix
            '%d', // action
            '%d', // type
            '%d', // source
            '%s', // description
            '%s', // date_created
            '%s', // date_expires
            '%s', // date_updated
            '%s', // created_by
        ];

        return ($wpdb->insert($table, $data, $fmt) !== false);
    }

    /**
     * Lagrer en ny regel for et land i databasen.
     *
     * @param string $country Landskode.
     * @param string $ruleAction Regel som gjelder for landet, allow/block.
     * @return bool TRUE dersom regel ble lagret i databasen, FALSE hvis ikke.
     */
    public function saveCountryRule(string $country, string $ruleAction): bool {

        global $wpdb;
        $table = $wpdb->prefix . 'wtb_firewall_ips';

        $nowUTC = \current_time('mysql', true);

        if ($ruleAction == 'allow') {
            $action = 1;
        } else if ($ruleAction == 'block') {
            $action = 2;
        }

        $data = [
            'enabled' => (int) 1,
            'ipv4_start' => (int) 0,
            'ipv4_end' => (int) 0,
            'action' => $action,
            'type' => (int) 0,
            'source' => (int) 2,
            'description' => $country,
            'date_created' => $nowUTC,
            'date_expires' => '',
            'date_updated' => '',
            'created_by' => \wp_get_current_user()->user_login,
        ];

        $fmt = [
            '%d', // enabled
            '%d', // ipv4_start
            '%d', // ipv4_end
            '%s', // action
            '%d', // type
            '%d', // source
            '%s', // description
            '%s', // date_created
            '%s', // date_expires
            '%s', // date_updated
            '%s', // created_by
        ];

        return $wpdb->insert($table, $data, $fmt);
    }

    /**
     * Returnerer alle firewall regler som har blitt manuelt lagt inn av bruker.
     * Dette inkluderer alle regler, uavhengig av om regelen er av/på eller utgått på dato.
     *
     * @return \WTBFirewall\Firewall\FirewallRule[] Array som inneholder alle resultatene fra databasen. NULL dersom feil ved henting av data.
     */
    public function getManualRules(): array {
        global $wpdb;
        $table = $wpdb->prefix . 'wtb_firewall_ips';

        $rules = $wpdb->get_results("SELECT * FROM {$table} WHERE source = 1 ORDER BY date_created DESC", ARRAY_A);

        if (!is_array($rules)) {
            return null;
        }

        return $this->createRuleSet($rules);
    }

    /**
     * Returnerer alle firewall regler som gjelder land.
     * Dette inkluderer alle regler, uavhengig av om regelen er av/på.
     *
     * @return \WTBFirewall\Firewall\FirewallRule[] Array som inneholder alle resultatene fra databasen. NULL dersom feil ved henting av data.
     */
    public function getCountryRules(): array {
        global $wpdb;
        $table = $wpdb->prefix . 'wtb_firewall_ips';

        $rules = $wpdb->get_results("SELECT * FROM {$table} WHERE source = 2 ORDER BY description ASC", ARRAY_A);

        if (!is_array($rules)) {
            return null;
        }

        return $this->createRuleSet($rules);
    }

    /**
     * Returnerer alle firewall regler som har blitt automatisk lagt til.
     * Dette inkluderer alle regler, uavhengig av om regelen er av/på eller utgått på dato.
     *
     * @return \WTBFirewall\Firewall\FirewallRule[] Array som inneholder alle resultatene fra databasen. NULL dersom feil ved henting av data.
     */
    public function getAutoRules(): array {
        global $wpdb;
        $table = $wpdb->prefix . 'wtb_firewall_ips';

        $rules = $wpdb->get_results("SELECT * FROM {$table} WHERE source = 3 ORDER BY date_created DESC", ARRAY_A);

        if (!is_array($rules)) {
            return null;
        }

        return $this->createRuleSet($rules);
    }

    /**
     * Returnerer en firewall regel med oppgitt id.
     *
     * @param int $ruleID ID for regel.
     * @return \WTBFirewall\Firewall\FirewallRule Firewall regel, null dersom ikke funnet.
     */
    public function getRule(int $ruleID): \WTBFirewall\Firewall\FirewallRule {
        global $wpdb;
        $table = $wpdb->prefix . 'wtb_firewall_ips';

        $rule = $wpdb->get_row("SELECT * FROM {$table} WHERE ID=$ruleID", ARRAY_A);

        if ($rule !== null) {
            return new \WTBFirewall\Firewall\FirewallRule($rule);
        }

        return null;
    }

    /**
     * Skriver alle manuelle og aktive regler til php fil brukt av firewall installert i .htaccess
     *
     * @return bool TRUE dersom alt gikk bra, FALSE dersom feil.
     */
    public function writeManualRulesToFile(): bool {

        $rules = $this->getManualRules();

        if ($rules === null) {
            return false;
        }

        //Plasser reglene i array basert på regel type.
        //Format på arrays er samme som brukt av firewall prepend php script.
        $ipv4AllowSingle = array();
        $ipv4BlockSingle = array();
        $ipv4AllowRange = array();
        $ipv4BlockRange = array();
        $ipv6Allow = array();
        $ipv6Block = array();

        foreach ($rules as $rule) {
            if ($rule->isActive() && $rule->isSingleIPv4() && $rule->isAllowRule()) {
                //IPv4 Allow
                $ipv4AllowSingle[$rule['ipv4_start']] = [$rule['id'], $rule['date_expires']];
            } else if ($rule->isActive() && $rule->isSingleIPv4() && $rule->isBlockRule()) {
                //IPv4 Block
                $ipv4BlockSingle[$rule['ipv4_start']] = [$rule['id'], $rule['date_expires']];
            } else if ($rule->isActive() && $rule->isIPv4Range() && $rule->isAllowRule()) {
                //IPv4 Range Allow
                $ipv4AllowRange[] = [$rule['ipv4_start'], $rule['ipv4_end'], $rule['id'], $rule['date_expires']];
            } else if ($rule->isActive() && $rule->isIPv4Range() && $rule->isBlockRule()) {
                //IPv4 Range Block
                $ipv4BlockRange[] = [$rule['ipv4_start'], $rule['ipv4_end'], $rule['id'], $rule['date_expires']];
            } else if ($rule->isActive() && $rule->isCIDR() && $rule->isAllowRule()) {
                //IPv4 CIDR Allow
                $networkStart = $rule['ipv4_start'];
                $networkEnd = ((2 ** (32 - $rule['ipv4_end'])) - 1) | $networkStart;
                $ipv4AllowRange[] = [$networkStart, $networkEnd, $rule['id'], $rule['date_expires']];
            } else if ($rule->isActive() && $rule->isCIDR() && $rule->isBlockRule()) {
                //IPv4 CIDR Block
                $networkStart = $rule['ipv4_start'];
                $networkEnd = ((2 ** (32 - $rule['ipv4_end'])) - 1) | $networkStart;
                $ipv4BlockRange[] = [$networkStart, $networkEnd, $rule['id'], $rule['date_expires']];
            } else if ($rule->isActive() && $rule->isIPv6() && $rule->isAllowRule()) {
                //IPv6 Allow
                $network = bin2hex($rule['ipv6']);
                $ipv6Allow[$rule['ipv6_prefix']][$network] = [$rule['id'], $rule['date_expires']];
            } else if ($rule->isActive() && $rule->isIPv6() && $rule->isBlockRule()) {
                //IPv6 Block
                $network = bin2hex($rule['ipv6']);
                $ipv6Block[$rule['ipv6_prefix']][$network] = [$rule['id'], $rule['date_expires']];
            }
        }

        //Sorter IP ranges på start IP.
        usort($ipv4AllowRange, function ($a, $b) {
            return $a[0] <=> $b[0];
        });

        usort($ipv4BlockRange, function ($a, $b) {
            return $a[0] <=> $b[0];
        });

        //Finn og sorter alle IPv6 nettverk prefixes.
        $ipv6AllowPrefix = array_keys($ipv6Allow);
        rsort($ipv6AllowPrefix, SORT_NUMERIC);

        $ipv6BlockPrefix = array_keys($ipv6Block);
        rsort($ipv6BlockPrefix, SORT_NUMERIC);

        $script = '<?php' . "\n" .
                '$ipAllowListe = ' . var_export($ipv4AllowSingle, true) . ";\n\n" .
                '$ipBlockListe = ' . var_export($ipv4BlockSingle, true) . ";\n\n" .
                '$ipAllowRange = ' . var_export($ipv4AllowRange, true) . ";\n\n" .
                '$ipBlockRange = ' . var_export($ipv4BlockRange, true) . ";\n\n" .
                "?>";

        $ipv6script = '<?php' . "\n" .
                '$ipv6AllowPrefix = ' . var_export($ipv6AllowPrefix, true) . ";\n\n" .
                '$ipv6AllowListe = ' . var_export($ipv6Allow, true) . ";\n\n" .
                '$ipv6BlockPrefix = ' . var_export($ipv6BlockPrefix, true) . ";\n\n" .
                '$ipv6BlockListe = ' . var_export($ipv6Block, true) . ";\n\n" .
                "?>";

        $this->writeToFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_rules.php', $script);
        $this->writeToFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_rules_ipv6.php', $ipv6script);

        return true;
    }

    /**
     * Sletter en firewall regel.
     *
     * @param int $ruleID Unikt ID for regel.
     * @return bool TRUE dersom OK, FALSE dersom feil.
     */
    public function deleteFirewallRule(int $ruleID): bool {

        global $wpdb;

        $tableName = $wpdb->prefix . 'wtb_firewall_ips';

        $result = $wpdb->delete(
                $tableName,
                array('id' => $ruleID),
                array('%d')
        );

        if ($result === false) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Aktiverer/Deaktiverer en regel.
     *
     * @param int $ruleID Unikt ID for regel.
     * @param bool $enabled TRUE=Regel skal aktiveres, FALSE=Regel skal deaktiveres.
     *
     * @return bool TRUE dersom OK, FALSE dersom feil.
     */
    public function enableDisableFirewallRule(int $ruleID, bool $enabled): bool {

        global $wpdb;

        $tableName = $wpdb->prefix . 'wtb_firewall_ips';

        if ($enabled) {
            $state = 1;
        } else {
            $state = 0;
        }

        $result = $wpdb->update(
                $tableName,
                array('enabled' => $state),
                array('id' => $ruleID),
                array('%d'),
                array('%d')
        );

        if ($result === false) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Skriver alle aktive lands regler til fil.
     *
     * @return bool TRUE dersom reglene ble skrevet, FALSE hvis ikke.
     */
    public function writeCountryRulesToFile(): bool {

        $rules = $this->getCountryRules();

        if ($rules === null) {
            return false;
        }

        $arrayAllow = array();
        $arrayBlock = array();
        $ipv6Allow = array();
        $ipv6Block = array();

        foreach ($rules as $rule) {
            if ($rule['enabled'] == 1) {
                $ipv4List = $this->getIpV4zone($rule['description']);
                $ipv6List = $this->getIpV6zone($rule['description']);

                if ($ipv4List !== null) {
                    foreach ($ipv4List as $range) {
                        if ($rule['action'] == 1) {
                            $arrayAllow[] = [$range['start'], $range['end'], $rule['id']];
                        } else if ($rule['action'] == 2) {
                            $arrayBlock[] = [$range['start'], $range['end'], $rule['id']];
                        }
                    }
                }

                if ($ipv6List !== null) {
                    foreach ($ipv6List as $ipv6) {
                        if ($rule['action'] == 1) {
                            $network = bin2hex($ipv6['ipv6']);
                            $ipv6Allow[$ipv6['prefix']][$network] = [$rule['id'], ''];
                        } else if ($rule['action'] == 2) {
                            $network = bin2hex($ipv6['ipv6']);
                            $ipv6Block[$ipv6['prefix']][$network] = [$rule['id'], ''];
                        }
                    }
                }
            }
        }

        //Sorter IPv4 nettverk.
        $sortColumnAllow = array_column($arrayAllow, 0);
        array_multisort($sortColumnAllow, SORT_ASC, $arrayAllow);

        $sortColumnBlock = array_column($arrayBlock, 0);
        array_multisort($sortColumnBlock, SORT_ASC, $arrayBlock);

        //Finn og sorter alle IPv6 nettverk prefixes.
        $ipv6AllowPrefix = array_keys($ipv6Allow);
        rsort($ipv6AllowPrefix, SORT_NUMERIC);

        $ipv6BlockPrefix = array_keys($ipv6Block);
        rsort($ipv6BlockPrefix, SORT_NUMERIC);

        $script = '<?php' . "\n" .
                '$countryBlockListe = ' . var_export($arrayBlock, true) . ";\n\n" .
                '$countryAllowListe = ' . var_export($arrayAllow, true) . ";\n\n" .
                '?>';

        $ipv6script = '<?php' . "\n" .
                '$ipv6CountryAllowPrefix = ' . var_export($ipv6AllowPrefix, true) . ";\n\n" .
                '$ipv6CountryAllowListe = ' . var_export($ipv6Allow, true) . ";\n\n" .
                '$ipv6CountryBlockPrefix = ' . var_export($ipv6BlockPrefix, true) . ";\n\n" .
                '$ipv6CountryBlockListe = ' . var_export($ipv6Block, true) . ";\n\n" .
                "?>";

        $this->writeToFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_country_rules.php', $script);
        $this->writeToFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_country_rules_ipv6.php', $ipv6script);

        return true;
    }

    /**
     * Leser alle IP range fra en IPv4 zone fil.
     *
     * @param string $countrycode Landskode for zone filen.
     * @return array Assosiativt array som inneholder alle IP ranges i zone fil. Lagret som INTs.
     *               Format [n]['start'] = Start på IP range, [n]['end'] = Slutten på IP range.
     *         null Dersom feil ved lesing av fil.
     */
    private function getIpV4zone(string $countrycode): ?array {
        require_once ABSPATH . 'wp-admin/includes/file.php';

        global $wp_filesystem;

        if (!$wp_filesystem) {
            WP_Filesystem();
        }

        if (!$wp_filesystem) {
            return null;
        }

        $path = WTB_FIREWALL_PATH . 'IPv4zone/' . strtolower($countrycode) . '.zone';

        if (!$wp_filesystem->exists($path) || !$wp_filesystem->is_readable($path)) {
            return null;
        }

        $contents = $wp_filesystem->get_contents($path);
        if (false === $contents || '' === $contents) {
            return null;
        }

        $ipRanges = [];

        // Split into lines (handles \n, \r\n, \r).
        $lines = preg_split("/\r\n|\n|\r/", $contents);

        foreach ($lines as $line) {
            $line = trim($line);

            if ('' === $line) {
                continue; // skip empty lines
            }

            // Convert read network to IP range.
            $ip = $this->parseIpv4Input($line);

            if (is_array($ip) && isset($ip['start'], $ip['prefix'])) {
                $ipRanges[] = [
                    'start' => (int) $ip['start'],
                    'end' => (((2 ** (32 - (int) $ip['prefix'])) - 1) | (int) $ip['start']),
                ];
            }
        }

        return $ipRanges;
    }

    /**
     * Leser alle nettverk fra en IPv6 zone fil.
     *
     * @param string $countrycode Landskode for zone filen.
     * @return array Array som inneholder alle nettverk lest fil, i samme format som returnert fra parseIpv6Input()
     *         null Dersom feil ved lesing av fil.
     */
    private function getIpV6zone(string $countrycode): ?array {
        require_once ABSPATH . 'wp-admin/includes/file.php';

        global $wp_filesystem;

        if (!$wp_filesystem) {
            WP_Filesystem();
        }

        if (!$wp_filesystem) {
            return null;
        }

        $path = WTB_FIREWALL_PATH . 'IPv6zone/' . strtolower($countrycode) . '.zone';

        if (!$wp_filesystem->exists($path) || !$wp_filesystem->is_readable($path)) {
            return null;
        }

        $contents = $wp_filesystem->get_contents($path);
        if (false === $contents || '' === $contents) {
            return null;
        }

        $ipList = [];

        // Split file into lines safely across platforms.
        $lines = preg_split("/\r\n|\n|\r/", $contents);

        foreach ($lines as $line) {
            $line = trim($line);

            if ('' === $line) {
                continue;
            }

            $ip = $this->parseIpv6Input($line);
            if (is_array($ip)) {
                $ipList[] = $ip;
            }
        }

        return $ipList;
    }

    /**
     * Sjekker om prepend scriptet for firewall skal installeres i .htaccess eller user.ini
     *
     * @return string Hvilken fil skal brukes: 'htaccess' eller 'user.ini'
     */
    private function installDetectPrependMethod(): string {

        //PHP-FPM / CGI / FastCGI
        $sapi = \php_sapi_name();

        if (\strpos($sapi, 'fpm') !== false ||
                \strpos($sapi, 'cgi') !== false ||
                isset($_SERVER['FCGI_ROLE'])) {

            return 'user.ini';
        }

        //MOD PHP
        if (function_exists('apache_get_modules')) {
            $modules = apache_get_modules();
            if (in_array('mod_php', $modules, true) || in_array('mod_php8', $modules, true)) {
                return 'htaccess';
            }
        }

        //Default til user.ini
        return 'user.ini';
    }

    /**
     * Oppdaterer .htaccess med link til php prepend scriptet.
     *
     * @return TRUE dersom alt gikk bra. FALSE hvis ikke.
     */
    private function installUpdateHtaccess(): bool {
        $markerStart = "# BEGIN WTB FIREWALL";
        $markerEnd = "# END WTB FIREWALL";

        $block = $markerStart . "\n"
                . "<IfModule mod_php.c>\n"
                . "    php_value auto_prepend_file \"" . $this->getPrependFile() . "\"\n"
                . "</IfModule>\n"
                . "<IfModule mod_php8.c>\n"
                . "    php_value auto_prepend_file \"" . $this->getPrependFile() . "\"\n"
                . "</IfModule>\n"
                . $markerEnd . "\n";

        $htaccessPath = ABSPATH . '.htaccess';

        $original = '';

        if (file_exists($htaccessPath)) {
            $original = file_get_contents($htaccessPath);
            if ($original === false) {
                return false;
            }

            //Fjern eventuell allerede eksisterende installasjon.
            $pattern = '/' . preg_quote($markerStart, '/') . '.*?' . preg_quote($markerEnd, '/') . '\s*/s';
            $original = preg_replace($pattern, '', $original);
        }

        //Legg til prepend scriptet på starten av .htaccess
        $newContent = $block . "\n" . ltrim($original, "\r\n");

        return file_put_contents($htaccessPath, $newContent) !== false;
    }

    /**
     * Oppdaterer user.ini med link til php prepend scriptet.
     *
     * @return bool TRUE dersom alt gikk bra. FALSE hvis ikke.
     */
    private function installUpdateUserIni(): bool {
        $markerStart = "; BEGIN WTB FIREWALL";
        $markerEnd = "; END WTB FIREWALL";

        $block = $markerStart . "\n"
                . 'auto_prepend_file="' . $this->getPrependFile() . '"' . "\n"
                . $markerEnd . "\n";

        $userIniPath = ABSPATH . '.user.ini';

        $original = '';

        if (file_exists($userIniPath)) {
            $original = file_get_contents($userIniPath);
            if ($original === false) {
                return false;
            }

            //Fjern eventuell eksisterende installasjon.
            $pattern = '/' . preg_quote($markerStart, '/') . '.*?' . preg_quote($markerEnd, '/') . '\s*/s';
            $original = preg_replace($pattern, '', $original);
        }

        //Legg til på slutten eller skriv ny fil.
        $original = rtrim($original, "\r\n");
        $newContents = $original === '' ? $block : $original . "\n\n" . $block;

        return file_put_contents($userIniPath, $newContents) !== false;
    }

    /**
     * Sjekker for midlertidige regler som er utgått på dato. Dersom vi finner regler
     * som er utgått på dato vil de bli deaktivert og firewall regel filene skrevet på nytt.
     *
     */
    public function checkTemporaryRules() {

        $rules = $this->getManualRules();

        //Brukt til å angi om vi har deaktivert minst en regel.
        $rulesUpdated = false;

        if ($rules === null) {
            return;
        }

        //Deaktiver eventuelle regler som er utgått på dato.
        foreach ($rules as $rule) {
            if ($rule['enabled'] && $rule->isExpired()) {
                $this->enableDisableFirewallRule($rule['id'], false);
                $rulesUpdated = true;
            }
        }

        if ($rulesUpdated) {
            $this->writeManualRulesToFile();
        }
    }

    /**
     * Sjekker om vi har flere midlertidige regler igjen.
     * Isåfall legg inn ny wp-cron jobb for regelen som går først ut på dato.
     *
     * NB! IKKE LENGRE BRUKT. DETTE GJØRES I DAGLIG VEDLIKEHOLD WP-CRON ISTEDENFOR.
     */
    public function checkTemporaryRulesScheduling() {

        $rules = $this->getManualRules();

        if ($rules === null) {
            return;
        }

        //Fjern eventuelt andre wp-cron jobs. Skal bare ha en av gangen.
        wp_clear_scheduled_hook('wtb_firewall_check_temp_rules');

        //DateTime, neste gang en midlertidig regel går ut på dato.
        $earliestDt = null;

        //Finn regelen som går først ut på dato.
        foreach ($rules as $rule) {
            if ($rule['enabled'] && $rule['date_expires'] !== null) {
                try {
                    $dt = new \DateTime($rule['date_expires'], new \DateTimeZone('UTC'));
                } catch (Exception $e) {
                    continue; // Ugylid date_expires, burde egentlig ikke kunne skje.
                }

                if ($earliestDt == null || $dt < $earliestDt) {
                    $earliestDt = $dt;
                }
            }
        }

        if ($earliestDt != null) {
            //Fant en midlertidig regel. Legg inn ny wp-cron jobb.
            $earliestDt->modify('+1 second');
            $timestamp = $earliestDt->getTimestamp();

            wp_schedule_single_event($timestamp, 'wtb_firewall_check_temp_rules');
        }
    }

    /**
     * Lagrer ny firewall regel relatert til automatisk blokkering.
     * Vil også legge inn ny wp-cron jobb for å lage firewall regel filen på nytt.
     *
     * @param string $ip IP addresse som skal blokkeres. Kun enkelt addresse, ingen ip range eller CIDR.
     * @param string $ruleNote Beskrivelse av regelen.
     * @param string $dateExpire Når regelen går ut på dato. Gitt som UTC+0 timezone i samme format som MySQL bruker.
     */
    public function saveAutoBlockRule(string $ip, string $ruleNote, string $dateExpire) {

        $nowUTC = \current_time('mysql', true);

        if (\WtbFirewall\Helpers\IpHelper::isValidIpv4($ip)) {
            $data = [
                'enabled' => (int) 1,
                'ipv4_start' => (int) ip2long($ip),
                'ipv4_end' => (int) 0,
                'action' => (int) 2,
                'type' => (int) 1,
                'source' => (int) 3,
                'description' => $ruleNote,
                'date_created' => $nowUTC,
                'date_expires' => $dateExpire,
                'date_updated' => '',
                'created_by' => '',
            ];

            $fmt = [
                '%d', // enabled
                '%d', // ipv4_start
                '%d', // ipv4_end
                '%s', // action
                '%d', // type
                '%d', // source
                '%s', // description
                '%s', // date_created
                '%s', // date_expires
                '%s', // date_updated
                '%s', // created_by
            ];
        } else if (\WtbFirewall\Helpers\IpHelper::isValidIpv6($ip)) {
            $data = [
                'enabled' => (int) 1,
                'ipv6' => inet_pton($ip),
                'ipv6_prefix' => (int) 128,
                'action' => (int) 2,
                'type' => (int) 4,
                'source' => (int) 3,
                'description' => $ruleNote,
                'date_created' => $nowUTC,
                'date_expires' => $dateExpire,
                'date_updated' => '',
                'created_by' => '',
            ];

            $fmt = [
                '%d', // enabled
                '%s', // ipv6
                '%d', // ipv6_prefix
                '%s', // action
                '%d', // type
                '%d', // source
                '%s', // description
                '%s', // date_created
                '%s', // date_expires
                '%s', // date_updated
                '%s', // created_by
            ];
        } else {
            //Ugyldig IP
            return false;
        }

        global $wpdb;
        $table = $wpdb->prefix . 'wtb_firewall_ips';

        $wpdb->insert($table, $data, $fmt);

        //Lag regel filen på nytt.
        $this->autoRulesScheduling();
    }

    /**
     * Legger inn en ny wp-cron jobb for å lage auto regel filen på nytt.
     * Dersom en wp-cron jobb allerede er lagt inn til å kjøre innen neste 5 sekunder blir ingenting gjort.
     */
    public function autoRulesScheduling() {

        $nextRun = \wp_next_scheduled('wtb_firewall_create_auto_rules');

        if ($nextRun && $nextRun < time()) {
            //Fjern alle gamle wp-cron jobber.
            \wp_unschedule_event($nextRun, 'wtb_firewall_create_auto_rules');
            $this->autoRulesScheduling();
            return;
        }

        if (!$nextRun || $nextRun > (time() + 5)) {
            \wp_schedule_single_event(time() + 5, 'wtb_firewall_create_auto_rules');
        }
    }
    
    /**
     * Legger inn en ny wp-cron jobb for å lage regel filene for land på nytt.
     * Dersom en wp-cron jobb allerede er lagt inn til å kjøre innen neste 5 sekunder blir ingenting gjort.     
     */
     public function countryRulesScheduling() {
         
         $nextRun = \wp_next_scheduled('wtb_firewall_create_country_rules');

        if ($nextRun && $nextRun < time()) {
            //Fjern alle gamle wp-cron jobber.
            \wp_unschedule_event($nextRun, 'wtb_firewall_create_country_rules');
            $this->countryRulesScheduling();
            return;
        }

        if (!$nextRun || $nextRun > (time() + 5)) {
            \wp_schedule_single_event(time() + 5, 'wtb_firewall_create_country_rules');
        }
     }

    /**
     * Oppdaterer en eksisterende firewall regel.
     *
     * @param \WtbFirewall\Firewall\FirewallRule $rule Regelen som skal oppdateres. Alle feltene som er satt i $rule vil bli oppdatert.
     *                    Feltet 'id' må være satt,  'date_updated' vil settes til nåværende tidspunkt.
     *
     * @return bool TRUE dersom OK og firewall regel ble oppdatert, FALSE hvis ikke.
     */
    public function updateRule(\WtbFirewall\Firewall\FirewallRule $rule): bool {
        global $wpdb;

        $table = $wpdb->prefix . 'wtb_firewall_ips';
        $id = isset($rule['id']) ? (int) $rule['id'] : 0;

        if ($id <= 0) {
            return false;
        }

        // field => format
        $fields = [
            'enabled' => '%d',
            'ipv4_start' => '%d',
            'ipv4_end' => '%d',
            'ipv6' => '%s',
            'ipv6_prefix' => '%d',
            'action' => '%d',
            'type' => '%d',
            'source' => '%d',
            'description' => '%s',
            'date_expires' => '%s',
            'created_by' => '%s',
            'hits' => '%d',
            'date_last_hit' => '%s',
        ];

        $data = [];
        $format = [];

        foreach ($fields as $field => $fmt) {
            if (!isset($rule[$field])) {
                continue;
            }

            $value = $rule[$field];

            // Basic casting for integer fields.
            if ('%d' === $fmt) {
                $value = (int) $value;
            }

            $data[$field] = $value;
            $format[] = $fmt;
        }

        // Always update timestamp.
        $data['date_updated'] = gmdate('Y-m-d H:i:s');
        $format[] = '%s';

        if (empty($data)) {
            return false;
        }

        $result = $wpdb->update(
                $table,
                $data,
                ['id' => $id],
                $format,
                ['%d']
        );

        return false !== $result;
    }

    /**
     * Skriver auto regler til fil.
     *
     * @return bool TRUE dersom OK, FALSE hvis feil.
     */
    public function writeAutoRulesToFile() {

        $rules = $this->getAutoRules();

        if ($rules === null) {
            return false;
        }

        //Her lagrer vi alle IPv4 addressene som skal blokkeres.
        $ipv4Block = array();
        $ipv6Block = array();

        foreach ($rules as $rule) {
            if ($rule->isActive() && $rule['type'] == 1 && $rule['action'] == 2) {
                //IPv4 Block
                $ipv4Block[$rule['ipv4_start']] = [$rule['id'], $rule['date_expires']];
            } else if ($rule->isActive() && $rule['type'] == 4 && $rule['action'] == 2) {
                //IPv6 Block
                $network = bin2hex($rule['ipv6']);
                $ipv6Block[$rule['ipv6_prefix']][$network] = [$rule['id'], $rule['date_expires']];
            }
        }

        //Finn og sorter alle IPv6 prefix.
        $ipv6BlockPrefix = array_keys($ipv6Block);
        rsort($ipv6BlockPrefix, SORT_NUMERIC);

        $script = '<?php' . "\n" .
                '$ipAutoBlock = ' . var_export($ipv4Block, true) . ";\n\n?>";

        $ipv6script = '<?php' . "\n" .
                '$ipv6AutoBlockPrefix = ' . var_export($ipv6BlockPrefix, true) . ";\n\n" .
                '$ipv6AutoBlockListe = ' . var_export($ipv6Block, true) . ";\n\n" .
                "?>";

        $this->writeToFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_auto_rules.php', $script);
        $this->writeToFile(WTB_FIREWALL_UPLOAD_PATH . 'wtb_firewall_auto_rules_ipv6.php', $ipv6script);

        return true;
    }

    /**
     * Går gjennom alle automatiske regler og sjekker om noen av dem er utgått på dato.
     * Isåfall vil de bli slettet og regel filen laget på nytt.
     */
    public function autoRulesCleanup() {

        $rules = $this->getAutoRules();

        $ruleDeleted = false;

        //Fjern alle utgåtte automatiske regler.
        foreach ($rules as $rule) {
            if ($rule->isExpired()) {
                $this->deleteFirewallRule($rule['id']);
                $ruleDeleted = true;
            }
        }

        if ($ruleDeleted) {
            $this->autoRulesScheduling();
        }
    }

    /**
     * Sjekker om det allerede finnes en aktiv automatisk generert regel for oppgitt ip.
     *
     * @param string $ip IP dette gjelder.
     * @return bool TRUE dersom det finnes en regel og den er aktiv og ikke utgått på dato.
     */
    public function autoRulesExists(string $ip): bool {
        global $wpdb;

        $table = $wpdb->prefix . 'wtb_firewall_ips';

        $nowUtc = gmdate('Y-m-d H:i:s');

        if (\WtbFirewall\Helpers\IpHelper::isValidIpv4($ip)) {
            return (bool) $wpdb->get_var($wpdb->prepare("SELECT 1 FROM {$table} WHERE ipv4_start = %d AND enabled = %d AND action = %d AND source = %d AND date_expires > %s LIMIT 1", (int) ip2long($ip), 1, 2, 3, $nowUtc));
        } else if (\WtbFirewall\Helpers\IpHelper::isValidIpv6($ip)) {
            return (bool) $wpdb->get_var($wpdb->prepare("SELECT 1 FROM {$table} WHERE ipv6 = %s AND enabled = %d AND action = %d AND source = %d AND date_expires > %s LIMIT 1", inet_pton($ip), 1, 2, 3, $nowUtc));
        } else {
            //Ugyldig IP.
            return false;
        }
    }

    /**
     * Tar imot liste over firewall regler lest fra databasen og gjør om til FirewallRule objekter.
     *
     * @param array $rules Array med regler fra databasen.
     * @return \WTBFirewall\Firewall\FirewallRule[] Array av FirewallRule objekter som representerer reglene.
     */
    private function createRuleSet(array $rules): array {

        $firewallRules = array();

        foreach ($rules as $rule) {
            $firewallRules[] = new \WTBFirewall\Firewall\FirewallRule($rule);
        }

        return $firewallRules;
    }

    /**
     * Oppdaterer en midlertidig regel til å bli en permanent regel.
     *
     * @param \WtbFirewall\Firewall\FirewallRule $rule Firewall regel dette gjelder.
     *
     * @return bool TRUE dersom OK, FALSE hvis regel ikke ble gjort permanent.
     */
    public function makeTempRulePermanent(\WtbFirewall\Firewall\FirewallRule $rule): bool {
        $ruleId = (int) $rule['id'];
        if ($ruleId <= 0) {
            return false;
        }

        // Get username (fallback to empty string if no logged-in user)
        $user = wp_get_current_user();
        $username = (!empty($user) && !empty($user->user_login)) ? $user->user_login : '';

        global $wpdb;
        $table = $wpdb->prefix . 'wtb_firewall_ips';

        $updated = $wpdb->update(
                $table,
                array(
                    'source' => 1,
                    'date_expires' => null,
                    'created_by' => $username,
                ),
                array(
                    'id' => $ruleId,
                ),
                array(
                    '%d', // source
                    null, // date_expires -> NULL
                    '%s', // created_by
                ),
                array(
                    '%d', // id
                )
        );

        // false = DB error
        // 0 = no row changed
        // >0 = updated
        return ($updated !== false && $updated > 0);
    }
}
