<?php

declare(strict_types=1);

namespace App\Domain\Security\ModSecurity\Services;

use App\Domain\Account\Models\Account;
use App\Domain\Domain\Models\Domain;
use App\Domain\Security\ModSecurity\Exceptions\ModSecurityException;
use App\Domain\Sysapi\Contracts\SysapiBridge;
use Illuminate\Support\Facades\DB;

/**
 * Per-domain ModSecurity provisioner.
 *
 * Calls the onx-modsec-rule-write sysapi script with the live settings from
 * modsecurity_site_settings + the global modsecurity_settings table. After a
 * successful write we update last_provisioned_at on the row (best-effort).
 */
final class ModSecurityProvisioner
{
    public function __construct(private readonly SysapiBridge $sysapi) {}

    /**
     * Rebuild the overlay conf for a single domain.
     *
     * @return array<string, mixed>  The decoded sysapi result payload.
     *
     * @throws ModSecurityException
     */
    public function rebuildForDomain(Domain $domain): array
    {
        $account = Account::query()->find($domain->account_id);
        if ($account === null) {
            throw new ModSecurityException("Domain {$domain->name} has no parent account.");
        }

        $settings = $this->collectSettings($account, $domain);

        $result = $this->sysapi->run('modsec-rule-write', $settings);

        if ($result->failed()) {
            // Exit code 4 = configtest rejected + we rolled back. Treat as
            // distinct user-facing error so the UI can hint at the conf.
            if ($result->exitCode === 4) {
                throw ModSecurityException::rolledBack('rule-write', $result);
            }
            throw ModSecurityException::sysapiFailed('rule-write', $result);
        }

        return $result->data ?? [];
    }

    /**
     * Query the live engine/rule status from the host.
     *
     * @return array<string, mixed>
     *
     * @throws ModSecurityException
     */
    public function status(): array
    {
        $result = $this->sysapi->run('modsec-status', []);
        if ($result->failed()) {
            throw ModSecurityException::sysapiFailed('status', $result);
        }

        return $result->data ?? [];
    }

    /**
     * Build the JSON payload for onx-modsec-rule-write from the DB state.
     *
     * @return array<string, mixed>
     */
    private function collectSettings(Account $account, Domain $domain): array
    {
        $row = DB::table('modsecurity_site_settings')
            ->where('account_id', $account->id)
            ->where('domain_id', $domain->id)
            ->first();

        $enabled = $row ? (bool) $row->enabled : true;

        $exceptions = [];
        if ($row && $row->exceptions !== null) {
            $decoded = json_decode((string) $row->exceptions, true);
            if (is_array($decoded)) {
                $exceptions = array_values(array_filter(array_map(
                    fn ($e) => isset($e['rule_id']) ? (string) $e['rule_id'] : null,
                    $decoded
                )));
            }
        }

        // Global panel-wide engine + paranoia level fall back to safe defaults.
        $globalSettings = DB::table('modsecurity_settings')->pluck('value', 'key')->toArray();

        $engineEnabled = (bool) ($globalSettings['engine_enabled'] ?? true);
        $globalEngine = $engineEnabled ? ($enabled ? 'On' : 'Off') : 'Off';

        $paranoia = (int) ($globalSettings['paranoia_level'] ?? 1);
        if ($paranoia < 1 || $paranoia > 4) {
            $paranoia = 1;
        }

        return [
            'domain' => $domain->name,
            'username' => $account->username,
            'enabled' => $enabled && $engineEnabled,
            'global_engine' => $globalEngine,
            'paranoia_level' => $paranoia,
            'excluded_rules' => $exceptions,
            'custom_rules' => [],
        ];
    }
}
