<?php

declare(strict_types=1);

namespace PpPhp\Core\Databases;

use PpPhp\Core\PlatformSettings;

/**
 * Resolves and ENFORCES the maximum size of a Virtual Database.
 *
 * The cap comes from the install's PLAN (Standard/Pro/Premium → an Owner-defined
 * MB figure), with an optional per-database override. Unlike MySQL, SQLite lets us
 * make the ceiling HARD: `PRAGMA max_page_count` × `page_size` is a real byte
 * ceiling the engine itself refuses to cross (writes fail with "disk is full",
 * reads keep working, and raising the cap lifts it instantly).
 *
 * cap semantics (bytes):  0 = unlimited.
 * per-DB override (`pp_virtual_databases.max_bytes`):
 *     0  → inherit the plan cap,  <0 → force unlimited,  >0 → that many bytes.
 */
final class VirtualDbSizePolicy
{
    private const MB = 1048576;

    /** The plan slug this install runs on (from the signed lease, else owner default). */
    public static function currentPlanSlug(): string
    {
        // A licensed client learns its plan from the signed lease.
        try {
            if (class_exists(\PpPhp\Core\Licensing\LicenseClient::class)) {
                $st = (new \PpPhp\Core\Licensing\LicenseClient())->status();
                if (!empty($st['plan'])) return (string) $st['plan'];
            }
        } catch (\Throwable $e) { /* fall through */ }
        // The authority/owner install (no client lease) uses an owner-pinned plan.
        $bag = PlatformSettings::get('vdb_current_plan', ['slug' => 'premium']);
        $slug = (string) ($bag['slug'] ?? 'premium');
        return $slug !== '' ? $slug : 'premium';
    }

    /** The plan's max DB size in MB (null = unlimited). Owner-defined. */
    public static function planCapMb(?string $slug = null): ?int
    {
        $slug = $slug ?? self::currentPlanSlug();
        // On the authority/owner install PlanRegistry is present — the source of truth.
        try {
            if (class_exists(\PpPhp\Core\Licensing\PlanRegistry::class)) {
                return \PpPhp\Core\Licensing\PlanRegistry::maxDbMbFor($slug);
            }
        } catch (\Throwable $e) { /* fall through */ }
        // A pure client mirrors the per-plan caps the Owner pushed down.
        $caps = PlatformSettings::get('vdb_plan_caps', []);
        if (isset($caps[$slug]) && $caps[$slug] !== '' && $caps[$slug] !== null) {
            return max(0, (int) $caps[$slug]);
        }
        return null;
    }

    /** The effective byte cap for one pool DB row (0 = unlimited). */
    public static function capBytesFor(array $row): int
    {
        $override = (int) ($row['max_bytes'] ?? 0);
        if ($override < 0) { return 0; }                 // forced unlimited
        if ($override > 0) { return $override; }         // explicit per-DB cap
        $mb = self::planCapMb();
        if ($mb === null || $mb <= 0) { return 0; }      // plan = unlimited
        return $mb * self::MB;
    }

    /**
     * Apply the byte cap to a live SQLite connection via max_page_count. No-op when
     * unlimited. Safe/best-effort: a failure to set the pragma never breaks the query.
     */
    public static function applyTo(\PDO $pdo, int $capBytes): void
    {
        if ($capBytes <= 0) { return; }
        try {
            $pageSize = (int) $pdo->query('PRAGMA page_size')->fetchColumn();
            if ($pageSize <= 0) { $pageSize = 4096; }
            $pages = (int) max(1, (int) ceil($capBytes / $pageSize));
            $pdo->exec('PRAGMA max_page_count = ' . $pages);
        } catch (\Throwable $e) { /* best effort */ }
    }

    /**
     * A usage snapshot for the UI meter.
     * @return array{bytes:int,cap:int,unlimited:bool,percent:float,level:string,label:string,message:string}
     */
    public static function usage(int $bytes, int $capBytes): array
    {
        if ($capBytes <= 0) {
            return ['bytes' => $bytes, 'cap' => 0, 'unlimited' => true, 'percent' => 0.0,
                    'level' => 'ok', 'label' => self::human($bytes) . ' · no limit', 'message' => ''];
        }
        $pct = min(999.0, ($bytes / $capBytes) * 100);
        if ($bytes >= $capBytes)      { $level = 'over';     $msg = 'Full — writes are paused. Free some space, or ask your admin to raise the limit.'; }
        elseif ($pct >= 90)           { $level = 'critical'; $msg = 'Almost full — ' . self::human(max(0, $capBytes - $bytes)) . ' left.'; }
        elseif ($pct >= 75)           { $level = 'warn';     $msg = self::human(max(0, $capBytes - $bytes)) . ' of headroom left.'; }
        else                          { $level = 'ok';       $msg = self::human(max(0, $capBytes - $bytes)) . ' free.'; }
        return ['bytes' => $bytes, 'cap' => $capBytes, 'unlimited' => false, 'percent' => round($pct, 1),
                'level' => $level, 'label' => self::human($bytes) . ' / ' . self::human($capBytes), 'message' => $msg];
    }

    public static function human(int $b): string
    {
        if ($b <= 0) return '0 B';
        $u = ['B', 'KB', 'MB', 'GB'];
        $i = max(0, min((int) floor(log($b, 1024)), 3));
        return round($b / (1024 ** $i), $i >= 2 ? 1 : 0) . ' ' . $u[$i];
    }
}
