<?php

namespace WtbFirewall\Firewall;

use ArrayAccess;
use DateTimeImmutable;
use DateTimeZone;
use InvalidArgumentException;

class FirewallRule implements ArrayAccess {

    protected array $data = [];

    /**
     *
     *
     * @param array $row Array som inneholder firewall regel lest fra databasen.
     */
    public function __construct(array $row) {
        if (empty($row['id'])) {
            throw new InvalidArgumentException('Firewall rule must have an ID.');
        }

        $this->data = $this->normalize($row);
    }

    /**
     * Normalize and apply defaults
     */
    protected function normalize(array $row): array {
        return array_merge([
            'id' => null,
            'enabled' => 0,
            'ipv4_start' => 0,
            'ipv4_end' => 0,
            'ipv6_start' => null,
            'ipv6_end' => null,
            'action' => 0,
            'type' => 0,
            'source' => 0,
            'description' => '',
            'date_created' => null,
            'date_expires' => null,
            'date_updated' => null,
            'created_by' => '',
            'hits' => 0,
            'date_last_hit' => null,
                ], $row);
    }

    /* -----------------------------------------------------------------
     * ArrayAccess implementation
     * ----------------------------------------------------------------- */

    public function offsetExists(mixed $offset): bool {
        return isset($this->data[$offset]);
    }

    public function offsetGet(mixed $offset): mixed {
        return $this->data[$offset] ?? null;
    }

    public function offsetSet(mixed $offset, mixed $value): void {
        if ($offset === null) {
            throw new InvalidArgumentException('Offset must be specified.');
        }

        $this->data[$offset] = $value;
    }

    public function offsetUnset(mixed $offset): void {
        unset($this->data[$offset]);
    }

    /* -----------------------------------------------------------------
     * Rule state helpers
     * ----------------------------------------------------------------- */

    public function isEnabled(): bool {
        return (int) $this->data['enabled'] === 1;
    }

    public function enable(): void {
        $this->data['enabled'] = 1;
    }

    public function disable(): void {
        $this->data['enabled'] = 0;
    }

    public function isAllowRule(): bool {
        return (int) $this->data['action'] === 1;
    }

    public function isBlockRule(): bool {
        return (int) $this->data['action'] === 2;
    }

    /* -----------------------------------------------------------------
     * Expiration handling
     * ----------------------------------------------------------------- */

    public function hasExpiration(): bool {
        return !empty($this->data['date_expires']);
    }

    public function isExpired(): bool {
        if (!$this->hasExpiration()) {
            return false;
        }

        $now = new DateTimeImmutable('now', new DateTimeZone('UTC'));
        $expires = new DateTimeImmutable($this->data['date_expires'], new DateTimeZone('UTC'));

        return $expires <= $now;
    }

    public function isActive(): bool {
        return $this->isEnabled() && !$this->isExpired();
    }

    /* -----------------------------------------------------------------
     * IP helpers
     * ----------------------------------------------------------------- */

    public function isIPv4(): bool {
        return in_array((int) $this->data['type'], [1, 2, 3], true);
    }

    public function isSingleIPv4(): bool {
        return (int) $this->data['type'] === 1;
    }

    public function isIPv4Range(): bool {
        return (int) $this->data['type'] === 2;
    }

    public function isCIDR(): bool {
        return (int) $this->data['type'] === 3;
    }
    
    public function isIPv6() : bool {
        return (int) $this->data['type'] === 4;
    }

    /* -----------------------------------------------------------------
     * Utility
     * ----------------------------------------------------------------- */

    public function toArray(): array {
        return $this->data;
    }

    /**
     * Returnerer en string som representerer IP/Range/CIDR som kan vises til bruker.
     * 
     * NB!!! For øyeblikket er kun IPv4 støttet.
     *      
     * @return string IP/Range/CIDR som tekst for å vise til bruker.
     */
    public function getIpString(): string {
        switch ($this->data['type']) {
            case 1:
                //IPv4 addresse.
                return long2ip($this->data['ipv4_start']);
            case 2:
                //IPv4 range.
                return long2ip($this->data['ipv4_start']) . '-' . long2ip($this->data['ipv4_end']);
            case 3:
                //IPv4 CIDR.
                return long2ip($this->data['ipv4_start']) . '/' . $this->data['ipv4_end'];
            case 4:
                //IPv6
                if ($this->data['ipv6_prefix'] == 128) {
                    return inet_ntop($this->data['ipv6']);
                } else {
                    return inet_ntop($this->data['ipv6']) . '/' . $this->data['ipv6_prefix'];
                }
            default:
                return '';
        }
    }

    /**
     * Gjør firewall regel datoer om til rett timezone. 
     * 
     * @param string $time Dato som lagret i databasen, UTC+0 timezone. Kan være tom.
     * @return string Dato+tid i samme timezone som server bruker eller eller null dersom $time ikke er gyldig dato.
     */
    private function getLocalTime($time): ?string {
        if (!empty($time) && $time !== '0000-00-00 00:00:00') {
            $localTimeCreated = new \DateTime($time, new \DateTimeZone('UTC'));
            $localTimeCreated->setTimeZone(\wp_timezone());
            return $localTimeCreated->format('Y-m-d H:i:s');
        } else {
            return null;
        }
    }

    /**
     * 
     * @return string DATETIME format string som forteller når regel ble opprettet.
     *                  Gitt som lokaltid.
     */
    public function getLocalTimeAdded(): ?string {
        return $this->getLocalTime($this->data['date_created']);
    }

    /**
     * 
     * @return string DATETIME format string som forteller når regel sist blokkerte en tilkobling.
     *                  Dersom regel har aldri blokkert noe returneres en tom string.
     *                  Gitt som lokaltid.
     */
    public function getLocalTimeLastHit(): string {
        $time = $this->getLocalTime($this->data['date_last_hit']);

        if ($time == null) {
            return '';
        }

        return $time;
    }

    /**
     * 
     * @return string DATETIME format string som forteller når regel går ut på dato.
     *                  Dersom regel er permanent returneres en tom string ''.
     *                  Gitt som lokaltid.
     */
    public function getLocalTimeExpires(): string {
        $time = $this->getLocalTime($this->data['date_expires']);

        if ($time == null || !$this->hasExpiration()) {
            return '';
        }

        return $time;
    }
}
