<?php
namespace EasyChat\Library;

use EasyChat\Model\Setting;









final class WidgetDomainPolicy {
    public const SETTING_BASE_DOMAIN = 'chat_widget_base_domain';

     
    private $base_domain;

    public function __construct(string $base_domain = '') {
        $base_domain = self::normalizeHost($base_domain);
        if (!self::isAllowedBaseDomain($base_domain)) {
            $base_domain = self::installHost();
        }

        $this->base_domain = $base_domain;
    }

    public static function fromSettings(?Setting $model_setting = null): self {
        $base_domain = '';
        if ($model_setting !== null) {
            $base_domain = (string)$model_setting->get(self::SETTING_BASE_DOMAIN, '');
        }

        return new self($base_domain);
    }

    public function baseDomain(): string {
        return $this->base_domain;
    }

    public static function installHost(): string {
        $host = '';
        if (defined('HTTP_SERVER')) {
            $host = (string)parse_url((string)HTTP_SERVER, PHP_URL_HOST);
        }
        if ($host === '') {
            $host = (string)(RequestContext::server()['HTTP_HOST'] ?? '');
        }

        return self::normalizeHost($host);
    }

    public static function normalizeHost(string $value): string {
        $value = trim($value);
        if ($value === '') {
            return '';
        }

        if (strpos($value, '://') !== false) {
            $parsed = parse_url($value);
            $value = is_array($parsed) && isset($parsed['host']) ? (string)$parsed['host'] : '';
        } else {
            $value = preg_split('/[\/?#]/', $value, 2)[0] ?? '';
        }

        $value = trim(strtolower($value));
        if ($value === '') {
            return '';
        }

        if ($value[0] === '[') {
            return '';
        }

        $value = preg_replace('/:\d+$/', '', $value) ?: $value;
        $value = trim($value, ". \t\n\r\0\x0B");
        if ($value === '') {
            return '';
        }

        if (preg_match('/[^\x20-\x7E]/', $value) && function_exists('idn_to_ascii')) {
            $flags = defined('IDNA_DEFAULT') ? IDNA_DEFAULT : 0;
            $variant = defined('INTL_IDNA_VARIANT_UTS46') ? INTL_IDNA_VARIANT_UTS46 : 0;
            $ascii = idn_to_ascii($value, $flags, $variant);
            if (is_string($ascii) && $ascii !== '') {
                $value = strtolower($ascii);
            }
        }

        return $value;
    }

    public static function isHostSyntaxSafe(string $host): bool {
        if ($host === '' || strlen($host) > 253) {
            return false;
        }
        if (filter_var($host, FILTER_VALIDATE_IP)) {
            return true;
        }
        if ($host === 'localhost') {
            return true;
        }

        return (bool)preg_match('/^(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))+$/', $host);
    }

    public static function isAllowedBaseDomain(string $host): bool {
        $host = self::normalizeHost($host);
        if (!self::isHostSyntaxSafe($host)) {
            return false;
        }
        if (filter_var($host, FILTER_VALIDATE_IP) || $host === 'localhost') {
            return true;
        }

        return !self::isPublicSuffix($host);
    }

    public function isHostAllowed(string $host): bool {
        $host = self::normalizeHost($host);
        if ($host === '' || $this->base_domain === '') {
            return false;
        }
        if ($host === $this->base_domain) {
            return true;
        }
        if (filter_var($this->base_domain, FILTER_VALIDATE_IP) || $this->base_domain === 'localhost') {
            return false;
        }

        return substr($host, -strlen('.' . $this->base_domain)) === '.' . $this->base_domain;
    }

    public function isUrlAllowed(string $url): bool {
        $url = trim($url);
        if ($url === '') {
            return false;
        }

        $parts = parse_url($url);
        if (!is_array($parts) || empty($parts['host'])) {
            return false;
        }

        return $this->isHostAllowed((string)$parts['host']);
    }

    public function isRequestAllowed(array $server): bool {
        $origin_host = self::normalizeHost((string)($server['HTTP_ORIGIN'] ?? ''));
        if ($origin_host !== '' && $this->isHostAllowed($origin_host)) {
            return true;
        }

        $referer_host = self::normalizeHost((string)($server['HTTP_REFERER'] ?? ''));
        return $referer_host !== '' && $this->isHostAllowed($referer_host);
    }

    public static function isPublicSuffix(string $host): bool {
        $host = self::normalizeHost($host);
        if ($host === '' || filter_var($host, FILTER_VALIDATE_IP) || $host === 'localhost') {
            return false;
        }

        return self::publicSuffix($host) === $host;
    }

    public static function publicSuffix(string $host): string {
        $host = self::normalizeHost($host);
        if ($host === '' || strpos($host, '.') === false) {
            return $host;
        }

        $labels = explode('.', $host);
        $rules = self::publicSuffixRules();
        $label_count = count($labels);

        for ($i = 0; $i < $label_count; $i++) {
            $candidate = implode('.', array_slice($labels, $i));
            if (isset($rules['exception'][$candidate])) {
                return implode('.', array_slice($labels, $i + 1));
            }
        }

        $best = end($labels);
        $best_labels = 1;
        for ($i = 0; $i < $label_count; $i++) {
            $candidate = implode('.', array_slice($labels, $i));
            $candidate_labels = $label_count - $i;
            if (isset($rules['exact'][$candidate]) && $candidate_labels > $best_labels) {
                $best = $candidate;
                $best_labels = $candidate_labels;
            }

            if ($i + 1 < $label_count) {
                $wildcard = '*.' . implode('.', array_slice($labels, $i + 1));
                if (isset($rules['wildcard'][$wildcard]) && $candidate_labels > $best_labels) {
                    $best = $candidate;
                    $best_labels = $candidate_labels;
                }
            }
        }

        return $best;
    }

    


    private static function publicSuffixRules(): array {
        static $rules = null;
        if (is_array($rules)) {
            return $rules;
        }

        $rules = [
            'exact' => [],
            'wildcard' => [],
            'exception' => [],
        ];

        $path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'resources' . DIRECTORY_SEPARATOR . 'public_suffix_list.dat';
        foreach (is_file($path) ? (file($path, FILE_IGNORE_NEW_LINES) ?: []) : [] as $line) {
            $line = trim(strtolower($line));
            if ($line === '' || strpos($line, '//') === 0) {
                continue;
            }
            if (strpos($line, '!') === 0) {
                $rules['exception'][substr($line, 1)] = true;
                continue;
            }
            if (strpos($line, '*.') === 0) {
                $rules['wildcard'][$line] = true;
                continue;
            }
            $rules['exact'][$line] = true;
        }

        return $rules;
    }
}
