<?php
/**
 * Antara — read-only vault classification (master key candidates vs encrypted stores).
 * Used before creating a new master.key to avoid incompatible key + existing ciphertext.
 */

declare(strict_types=1);

if (!defined('ANTARA_ROOT')) {
    define('ANTARA_ROOT', dirname(__DIR__, 2));
}

require_once __DIR__ . '/paths.php';

final class AntaraVaultState
{
    public const MASTER_KEY_MIN_BYTES = 32;

    public const CASE_EMPTY = 'empty';
    public const CASE_ENC_WITHOUT_KEY = 'enc_without_key';
    public const CASE_NORMAL = 'normal';

    /**
     * Same candidate order as AntaraCrypto::masterKeyPath() (single source for both).
     *
     * @return list<string>
     */
    public static function masterKeyCandidatePaths(): array
    {
        $ds = DIRECTORY_SEPARATOR;
        $candidates = [];

        $custom = self::readExternalSecretsBasename();
        if ($custom !== null && $custom !== '') {
            $candidates[] = dirname(ANTARA_ROOT, 2) . $ds . $custom . $ds . 'master.key';
            $candidates[] = dirname(ANTARA_ROOT) . $ds . $custom . $ds . 'master.key';
        }

        $dyn = self::defaultExternalSecretsDirBasename();
        $candidates[] = dirname(ANTARA_ROOT, 2) . $ds . $dyn . $ds . 'master.key';
        $candidates[] = dirname(ANTARA_ROOT) . $ds . $dyn . $ds . 'master.key';

        $planLegacy = dirname(ANTARA_ROOT, 2) . $ds . 'antara_secrets';
        $candidates[] = $planLegacy . $ds . 'master.key';

        $siblingLegacy = dirname(ANTARA_ROOT) . $ds . 'antara_secrets';
        $candidates[] = $siblingLegacy . $ds . 'master.key';

        $internal = ANTARA_ROOT . $ds . 'storage' . $ds . 'secure';
        $candidates[] = $internal . $ds . '.master.key';

        return $candidates;
    }

    /**
     * Nombre de carpeta externa por defecto: estable para esta copia en disco (misma ruta en web y CLI).
     */
    public static function defaultExternalSecretsDirBasename(): string
    {
        $seed = str_replace('\\', '/', ANTARA_ROOT);
        $h = substr(hash('sha256', $seed), 0, 16);

        return 'fx_site_' . $h;
    }

    /**
     * Optional directory basename from installer: opaque fx_* or legacy …_secrets.
     */
    public static function readExternalSecretsBasename(): ?string
    {
        $path = AntaraPaths::externalSecretsBasenameFile();
        if (!is_file($path)) {
            return null;
        }
        $raw = trim((string) @file_get_contents($path));
        if ($raw === '' || strlen($raw) > 64 || !preg_match('/^[a-z0-9_]+$/', $raw)) {
            return null;
        }
        if (preg_match('/^fx_(site_)?[a-f0-9]{16}$/', $raw)) {
            return $raw;
        }
        if (str_ends_with($raw, '_secrets')) {
            return $raw;
        }
        return null;
    }

    /**
     * @return list<string>
     */
    public static function storeCiphertextPaths(): array
    {
        return [
            AntaraPaths::usersFile(),
            AntaraPaths::credsFile(),
            AntaraPaths::rateFile(),
            AntaraPaths::configFile(),
        ];
    }

    public static function masterKeyExistsAnywhere(): bool
    {
        $min = self::MASTER_KEY_MIN_BYTES;
        foreach (self::masterKeyCandidatePaths() as $path) {
            if (is_file($path) && (int) @filesize($path) >= $min) {
                return true;
            }
        }
        return false;
    }

    public static function hasNonEmptyStoreCiphertexts(): bool
    {
        foreach (self::storeCiphertextPaths() as $path) {
            if (is_file($path) && (int) @filesize($path) > 0) {
                return true;
            }
        }
        return false;
    }

    /**
     * Filesystem-only classification (does not decrypt).
     */
    public static function vaultBootstrapCase(): string
    {
        $hasKey = self::masterKeyExistsAnywhere();
        $hasEnc = self::hasNonEmptyStoreCiphertexts();

        if (!$hasKey && !$hasEnc) {
            return self::CASE_EMPTY;
        }
        if (!$hasKey && $hasEnc) {
            return self::CASE_ENC_WITHOUT_KEY;
        }
        return self::CASE_NORMAL;
    }

    /**
     * Web recovery UI is allowed when install is not finalized yet, or when an
     * operator created recovery.allow, or when ANTARA_RECOVERY_TOKEN is set (POST must still match for locked installs).
     */
    public static function recoveryWebAllowed(): bool
    {
        if (!is_file(AntaraPaths::installLockFile())) {
            return true;
        }
        if (is_file(AntaraPaths::recoveryAllowFile())) {
            return true;
        }
        $env = getenv('ANTARA_RECOVERY_TOKEN');
        return is_string($env) && $env !== '';
    }

    /**
     * When install.lock is present, require recovery.allow on disk OR matching env token in POST.
     */
    public static function recoveryPostAuthorized(string $postedToken): bool
    {
        if (!is_file(AntaraPaths::installLockFile())) {
            return true;
        }
        if (is_file(AntaraPaths::recoveryAllowFile())) {
            return true;
        }
        $env = getenv('ANTARA_RECOVERY_TOKEN');
        if (is_string($env) && $env !== '' && hash_equals($env, $postedToken)) {
            return true;
        }
        return false;
    }
}
