<?php

namespace App\Actions\Licensing;

use App\Actions\Installer\PersistLicense;
use App\Models\Company;
use App\Services\Licensing\LicenseClient;
use App\Services\Licensing\LicenseFingerprint;

/**
 * Background license re-check (docs/features/installer.md §6.3).
 *
 * Re-POSTs the stored license key to the server roughly every 30 days and
 * refreshes the cached details on the company row. The cadence guard lives
 * here (not in the scheduler) so it survives missed cron runs and a manual
 * "Re-check now" can force it.
 *
 * **A failed re-check NEVER locks the app.** We never hold the customer's data
 * hostage to a network call: an unreachable server only records the error (the
 * admin banner shows it); a server that actively rejects the key flips the
 * status to `invalid` but the app keeps running — the banner is the only
 * consequence.
 *
 * Offline-activated installs (no online key) are skipped — there's no server
 * for them to reach.
 */
class RecheckLicense
{
    /** Re-check at most this often unless forced. */
    private const INTERVAL_DAYS = 30;

    public function __construct(
        private readonly LicenseClient $client,
        private readonly PersistLicense $persist,
    ) {}

    /**
     * @return array{ok: bool, skipped?: bool, reason?: string, status?: string, error?: string}
     */
    public function __invoke(bool $force = false): array
    {
        // License enforcement disabled (config/pos.php → license.required):
        // nothing to verify, ever.
        if (! config('pos.license.required')) {
            return ['ok' => true, 'skipped' => true, 'reason' => 'disabled'];
        }

        // Panel-side re-verification switched off (config/pos.php →
        // license.recheck). The installer already validated the code once; the
        // running app doesn't re-litigate it, so there's nothing to do here —
        // not even on a forced "Re-check now".
        if (! config('pos.license.recheck')) {
            return ['ok' => true, 'skipped' => true, 'reason' => 'recheck_disabled'];
        }

        $company = Company::current();
        if ($company === null) {
            return ['ok' => false, 'skipped' => true, 'reason' => 'no_company'];
        }

        $key = trim((string) config('pos.license.key', ''));

        // Offline activation (or no key) → nothing to verify online. Leave the
        // status exactly as the offline blob set it.
        if ($key === '' || strtoupper($key) === 'OFFLINE') {
            return ['ok' => true, 'skipped' => true, 'reason' => 'offline', 'status' => $company->license_status];
        }

        // Cadence guard — only the scheduled (non-forced) path respects it.
        if (! $force && $this->checkedRecently($company)) {
            return ['ok' => true, 'skipped' => true, 'reason' => 'fresh', 'status' => $company->license_status];
        }

        $installUrl = (string) (config('app.url') ?: url('/'));

        $result = $this->client->check([
            'license_key' => $key,
            'install_url' => $installUrl,
            'fingerprint' => (string) config('pos.license.fingerprint', '') ?: LicenseFingerprint::make($installUrl),
            'version'     => (string) config('pos.version', '1.0.0'),
        ]);

        // Couldn't reach the server → record the error, keep the current status.
        // The app stays fully functional; the banner nudges the operator.
        if (! $result['ok']) {
            $company->forceFill([
                'license_last_checked_at' => now(),
                'license_last_error'      => $result['error'] ?? __('installer.license.errors.unreachable'),
            ])->save();
            forget_app_license();

            return ['ok' => false, 'error' => $result['error'] ?? null, 'status' => $company->license_status];
        }

        $body = $result['body'];

        if (($body['status'] ?? null) === 'valid') {
            // Refresh buyer/type/support window + clear any prior error.
            ($this->persist)($body);

            return ['ok' => true, 'status' => 'valid'];
        }

        // Server reached us and rejected the key. Flag invalid + keep the
        // message — but STILL never lock the app.
        $company->forceFill([
            'license_status'          => 'invalid',
            'license_last_checked_at' => now(),
            'license_last_error'      => $body['message'] ?? __('installer.license.errors.invalid'),
        ])->save();
        forget_app_license();

        return ['ok' => true, 'status' => 'invalid'];
    }

    private function checkedRecently(Company $company): bool
    {
        $last = $company->license_last_checked_at;

        return $last !== null && $last->diffInDays(now()) < self::INTERVAL_DAYS;
    }
}
