<?php
declare(strict_types=1);

namespace App\Services\Security;

use App\Core\Database;
use Throwable;

class LinkScanner
{
    private const DEFAULT_WHITELIST = [
        'drive.google.com',
        'dropbox.com',
        'onedrive.live.com',
        'mediafire.com',
        'sharemods.com',
        'mega.nz',
    ];

    private const DEFAULT_BLACKLIST = [
        'malware-test.local',
        'phishing-test.local',
        'scam-test.local',
    ];

    private const SHORTENERS = [
        'bit.ly',
        'tinyurl.com',
        't.co',
        'cutt.ly',
        'shorturl.at',
        'is.gd',
        'rebrand.ly',
    ];

    private const SUSPICIOUS_TERMS = [
        'phishing' => 55,
        'malware' => 65,
        'scam' => 50,
        'virus' => 55,
        'trojan' => 65,
        'keylogger' => 70,
        'stealer' => 60,
        'wallet' => 25,
        'seedphrase' => 60,
        'airdrop' => 35,
        'freecrypto' => 45,
        'crack' => 55,
        'keygen' => 60,
        'warez' => 65,
        'pirated' => 55,
        'illegaldownload' => 75,
        'adultcontent' => 65,
        'gambling' => 45,
        'casino' => 45,
    ];

    private array $trustedDomains = [];
    private array $blacklistedDomains = [];

    public function __construct(private array $config = [])
    {
        $configuredDomains = $config['trusted_domains'] ?? (array_is_list($config) ? $config : []);
        $this->trustedDomains = $this->normalizeDomains(array_merge(self::DEFAULT_WHITELIST, $configuredDomains, $this->loadDomains('domain_whitelist')));
        $this->blacklistedDomains = $this->normalizeDomains(array_merge(self::DEFAULT_BLACKLIST, $this->loadDomains('domain_blacklist')));
    }

    public function scan(string $url): array
    {
        $url = trim($url);

        if ($url === '' || filter_var($url, FILTER_VALIDATE_URL) === false) {
            return $this->format(100, ['invalid_url']);
        }

        $scheme = strtolower((string) parse_url($url, PHP_URL_SCHEME));
        if (!in_array($scheme, ['http', 'https'], true)) {
            return $this->format(100, ['unsupported_url_scheme']);
        }

        $domain = $this->extractDomain($url);
        if ($domain === null) {
            return $this->format(100, ['missing_or_invalid_domain']);
        }

        if ($this->isBlacklisted($domain)) {
            return $this->format(100, ['domain_blacklisted']);
        }

        $riskScore = 0;
        $reasons = [];

        if ($this->isWhitelisted($domain)) {
            $riskScore += 5;
            $reasons[] = 'domain_whitelisted';
        } else {
            $riskScore += 35;
            $reasons[] = 'domain_not_whitelisted';
        }

        if ($scheme === 'http') {
            $riskScore += 10;
            $reasons[] = 'insecure_http';
        }

        if ($this->isShortener($domain)) {
            $riskScore += 45;
            $reasons[] = 'url_shortener_detected';
        }

        if (filter_var($domain, FILTER_VALIDATE_IP)) {
            $riskScore += 30;
            $reasons[] = 'ip_address_domain';
        }

        if (str_contains($url, '@')) {
            $riskScore += 25;
            $reasons[] = 'userinfo_in_url';
        }

        if (substr_count($domain, '.') >= 4) {
            $riskScore += 10;
            $reasons[] = 'excessive_subdomains';
        }

        $normalizedUrl = $this->normalizeText($url);
        foreach (self::SUSPICIOUS_TERMS as $term => $weight) {
            if (str_contains($normalizedUrl, $term)) {
                $riskScore += $weight;
                $reasons[] = 'suspicious_term_' . $term;
            }
        }

        return $this->format($riskScore, $reasons);
    }

    public function extractDomain(string $url): ?string
    {
        $host = parse_url(trim($url), PHP_URL_HOST);
        if (!is_string($host) || trim($host) === '') {
            return null;
        }

        $host = strtolower(trim($host, " \t\n\r\0\x0B."));

        return preg_match('/^[a-z0-9.-]+$/', $host) ? $host : null;
    }

    public function isWhitelisted(string $domain): bool
    {
        return $this->matchesDomainList($domain, $this->trustedDomains);
    }

    public function isBlacklisted(string $domain): bool
    {
        return $this->matchesDomainList($domain, $this->blacklistedDomains);
    }

    public function isShortener(string $domain): bool
    {
        return $this->matchesDomainList($domain, self::SHORTENERS);
    }

    private function format(int $riskScore, array $reasons): array
    {
        $riskScore = min(100, max(0, $riskScore));
        $status = $riskScore >= 61 ? 'blocked' : ($riskScore >= 31 ? 'review' : 'safe');

        return [
            'risk_score' => $riskScore,
            'status' => $status,
            'action_taken' => match ($status) {
                'blocked' => 'blocked',
                'review' => 'pending_review',
                default => 'allow',
            },
            'reasons' => array_values(array_unique($reasons ?: ['no_link_risk_detected'])),
        ];
    }

    private function matchesDomainList(string $domain, array $list): bool
    {
        $domain = strtolower(trim($domain, '.'));

        foreach ($list as $listedDomain) {
            $listedDomain = strtolower(trim((string) $listedDomain, '.'));

            if ($listedDomain !== '' && ($domain === $listedDomain || str_ends_with($domain, '.' . $listedDomain))) {
                return true;
            }
        }

        return false;
    }

    private function normalizeDomains(array $domains): array
    {
        $normalized = [];

        foreach ($domains as $domain) {
            $domain = strtolower(trim((string) $domain));
            $domain = preg_replace('~^https?://~', '', $domain) ?: $domain;
            $domain = explode('/', $domain)[0] ?? $domain;
            $domain = trim($domain, '.');

            if ($domain !== '') {
                $normalized[] = $domain;
            }
        }

        return array_values(array_unique($normalized));
    }

    private function normalizeText(string $value): string
    {
        $value = strtolower($value);

        return preg_replace('/[^a-z0-9]+/', '', $value) ?: '';
    }

    private function loadDomains(string $table): array
    {
        try {
            $statement = Database::connection($this->config)->query("SELECT domain FROM {$table}");

            return array_map('strval', array_column($statement->fetchAll(), 'domain'));
        } catch (Throwable) {
            return [];
        }
    }
}
