<?php

namespace App\Services\Marketplace;

use App\Core\App;
use App\Models\MarketplaceInstalled;
use App\Models\Setting;

/**
 * ITSWEBER CMS – Marketplace Integrity Monitor
 *
 * Runs once per day (debounced via storage/temp/mp_integrity_last_run).
 * For each installed theme/plugin, recomputes SHA256 hashes of all files
 * and compares them against the baseline snapshot taken at install time.
 *
 * On drift detection:
 *  - stores a warning flag in iw_settings (marketplace_integrity_drift)
 *  - optionally emails the admin (setting: marketplace_integrity_notify = '1')
 *
 * Toggled via setting: marketplace_integrity_monitor_enabled = '1' (default on).
 */
class IntegrityMonitor
{
    private string $basePath;
    private string $lockFile;
    private int    $runInterval;

    public function __construct(string $basePath, int $runIntervalSeconds = 86400)
    {
        $this->basePath     = $basePath;
        $this->lockFile     = $basePath . '/storage/temp/mp_integrity_last_run';
        $this->runInterval  = $runIntervalSeconds;
    }

    /**
     * Run the integrity check if enough time has passed since last run.
     * Called from App::handle() in the pseudo-cron block – exits silently on error.
     */
    public function runIfDue(): void
    {
        // Guard: feature toggle
        if (Setting::get('marketplace_integrity_monitor_enabled', '1') !== '1') {
            return;
        }

        // Debounce: only run once per interval
        if (is_file($this->lockFile)) {
            $lastRun = (int) file_get_contents($this->lockFile);
            if ((time() - $lastRun) < $this->runInterval) {
                return;
            }
        }

        // Write lock immediately to prevent concurrent runs
        @file_put_contents($this->lockFile, (string) time());

        try {
            $this->run();
        } catch (\Throwable $e) {
            error_log('ITSWEBER IntegrityMonitor: ' . $e->getMessage());
        }
    }

    /**
     * Perform the full integrity check across all installed packages.
     */
    private function run(): void
    {
        $installed = MarketplaceInstalled::all('slug', 'ASC');
        if (empty($installed)) {
            Setting::set('marketplace_integrity_drift', '', 'marketplace');
            return;
        }

        $driftSlugs = [];

        foreach ($installed as $pkg) {
            $slug = $pkg['slug'] ?? '';
            $type = $pkg['type'] ?? 'plugin';

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

            $dir = $type === 'theme'
                ? $this->basePath . '/themes/' . $slug
                : $this->basePath . '/plugins/' . $slug;

            if (!is_dir($dir)) {
                // Package directory missing – count as drift
                $driftSlugs[] = $slug . ' (missing)';
                continue;
            }

            $snapshotFile = $this->basePath . '/storage/temp/mp_hash_' . $slug . '.json';

            $currentHash = $this->hashDirectory($dir);

            if (!is_file($snapshotFile)) {
                // No baseline yet – save current state as baseline
                @file_put_contents($snapshotFile, json_encode([
                    'slug'    => $slug,
                    'hash'    => $currentHash,
                    'created' => date('Y-m-d H:i:s'),
                ]));
                continue;
            }

            $snapshot = json_decode((string) file_get_contents($snapshotFile), true);
            $baseline = $snapshot['hash'] ?? '';

            if ($baseline !== '' && $baseline !== $currentHash) {
                $driftSlugs[] = $slug;
            }
        }

        if (empty($driftSlugs)) {
            Setting::set('marketplace_integrity_drift', '', 'marketplace');
            return;
        }

        $driftValue = implode(',', $driftSlugs);
        Setting::set('marketplace_integrity_drift', $driftValue, 'marketplace');

        if (Setting::get('marketplace_integrity_notify', '1') === '1') {
            $this->notifyAdmin($driftSlugs);
        }
    }

    /**
     * Compute a deterministic hash of all regular files in a directory tree.
     * Combines relative paths + file contents in alphabetical order.
     */
    private function hashDirectory(string $dir): string
    {
        $hashes = [];
        $iter   = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS)
        );

        foreach ($iter as $file) {
            if (!$file->isFile()) {
                continue;
            }
            $relative      = str_replace('\\', '/', substr($file->getPathname(), strlen($dir) + 1));
            $hashes[$relative] = hash_file('sha256', $file->getPathname());
        }

        ksort($hashes);

        return hash('sha256', json_encode($hashes));
    }

    /**
     * Send a notification email to the admin about detected drift.
     */
    private function notifyAdmin(array $driftSlugs): void
    {
        $app = App::getInstance();
        if (!$app || !$app->hasService('mailer')) {
            return;
        }

        try {
            $adminEmail = Setting::get('admin_email', '');
            if ($adminEmail === '') {
                return;
            }

            $siteName = Setting::get('site_name', 'ITSWEBER CMS');
            $subject  = '[' . $siteName . '] Marketplace Integrity Warning';
            $body     = "The following installed packages have been modified since installation:\n\n"
                      . implode("\n", array_map(fn($s) => '  • ' . $s, $driftSlugs))
                      . "\n\nPlease verify these packages in your admin panel.\n"
                      . "Admin panel: " . Setting::get('site_url', '') . "/admin\n";

            $mailer = $app->service('mailer');
            $mailer->send($adminEmail, $subject, nl2br(e($body)), strip_tags($body));
        } catch (\Throwable $ignored) {
            // Mail failure must not disrupt the main request
        }
    }
}
