namespace ZB.MOM.WW.OtOpcUa.Client.Shared;

/// <summary>
///     Resolves the canonical under-LocalAppData folder for the shared OPC UA client's PKI
///     store + persisted settings. Renamed from <c>LmxOpcUaClient</c> to <c>OtOpcUaClient</c>;
///     a one-shot migration shim moves a pre-rename folder in place on first
///     resolution so existing developer boxes keep their trusted server certs + saved
///     connection settings on upgrade.
/// </summary>
/// <remarks>
///     Thread-safe: the rename uses <see cref="Directory.Move"/> which is atomic on NTFS
///     within the same volume. The lock guarantees the migration runs at most once per
///     process even under concurrent first-touch from CLI + UI.
/// </remarks>
public static class ClientStoragePaths
{
    /// <summary>Canonical client folder name.</summary>
    public const string CanonicalFolderName = "OtOpcUaClient";

    /// <summary>Legacy folder name, used only by the migration shim.</summary>
    public const string LegacyFolderName = "LmxOpcUaClient";

    private static readonly Lock _migrationLock = new();
    private static bool _migrationChecked;

    /// <summary>
    ///     Absolute path to the client's top-level folder under LocalApplicationData. Runs the
    ///     one-shot legacy-folder migration before returning so callers that depend on this
    ///     path (PKI store, settings file) find their existing state at the canonical name.
    /// </summary>
    /// <returns>The absolute path to the client's canonical top-level folder.</returns>
    public static string GetRoot()
    {
        var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
        var canonical = Path.Combine(localAppData, CanonicalFolderName);
        MigrateLegacyFolderIfNeeded(localAppData, canonical);
        return canonical;
    }

    /// <summary>Subfolder for the application's PKI store — used by both CLI + UI.</summary>
    /// <returns>The absolute path to the client's PKI store folder.</returns>
    public static string GetPkiPath() => Path.Combine(GetRoot(), "pki");

    /// <summary>
    ///     Expose the migration probe for tests + for callers that want to check whether the
    ///     legacy folder still exists without forcing the rename. Returns true when a legacy
    ///     folder existed + was moved to canonical, false when no migration was needed or
    ///     canonical was already present.
    /// </summary>
    /// <returns><c>true</c> when a legacy folder existed and was moved to canonical; otherwise <c>false</c>.</returns>
    public static bool TryRunLegacyMigration()
    {
        var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
        var canonical = Path.Combine(localAppData, CanonicalFolderName);
        return MigrateLegacyFolderIfNeeded(localAppData, canonical);
    }

    private static bool MigrateLegacyFolderIfNeeded(string localAppData, string canonical)
    {
        // Fast-path out of the lock when the migration has already been attempted this process
        // — saves the IO on every subsequent call, + the migration is idempotent within the
        // same process anyway.
        if (_migrationChecked) return false;

        lock (_migrationLock)
        {
            if (_migrationChecked) return false;
            _migrationChecked = true;

            var legacy = Path.Combine(localAppData, LegacyFolderName);

            // Only migrate when the legacy folder is present + canonical isn't. Either of the
            // other three combinations (neither / only-canonical / both) means migration
            // should NOT run: no-op fresh install, already-migrated, or manual state the
            // developer has set up — don't clobber.
            if (!Directory.Exists(legacy)) return false;
            if (Directory.Exists(canonical)) return false;

            try
            {
                Directory.Move(legacy, canonical);
                return true;
            }
            catch (IOException)
            {
                // Concurrent another-process-moved-it or volume-boundary or permissions — leave
                // the legacy folder alone; callers that need it can either re-run migration
                // manually or point CertificateStorePath explicitly.
                return false;
            }
        }
    }
}
