using System;
using ArchestrA.GRAccess;

class Program
{
    static int Main(string[] args)
    {
        // Probe: write to $TestMachine.UpdateTestChangingInt.DeclarationsText (a
        // package-only ScriptExtension text field) via the TN-537 pattern using
        // ConfigurableAttributes. Read back through TWO paths to discriminate
        // between (a) write-side no-op vs (b) read-side staleness:
        //   1. Same IgObject proxy via obj.ConfigurableAttributes  (in-memory edit view)
        //   2. Fresh galaxy.QueryObjectsByName proxy via .Attributes (runtime view)
        //
        // Restores DeclarationsText to empty when done.

        const string GalaxyName  = "ZB";
        const string Tagname     = "$TestMachine";
        const string ScriptName  = "UpdateTestChangingInt";
        const string FieldName   = "DeclarationsText";
        const string Marker      = "// graccesscli probe marker — TN537 ConfigurableAttributes path\n";

        var user = Environment.GetEnvironmentVariable("MX_TEST_USER")     ?? "";
        var pass = Environment.GetEnvironmentVariable("MX_TEST_PASSWORD") ?? "";
        var node = Environment.MachineName;

        Console.WriteLine($"[probe] connecting to {node} galaxy={GalaxyName} as {user}");
        var gr = new GRAccessAppClass();
        var galaxies = gr.QueryGalaxies(node);
        var galaxy   = galaxies[GalaxyName];
        if (string.IsNullOrEmpty(user))
            galaxy.Login();
        else
            galaxy.Login(user, pass);

        var attrPath = ScriptName + "." + FieldName;

        // Acquire the target template object.
        var names = new string[] { Tagname };
        var initial = QueryOne(galaxy, names);

        // Read current value via ConfigurableAttributes (configuration view).
        var beforeCfg = TryReadString(initial.ConfigurableAttributes, attrPath, "before-cfg");
        var beforeRt  = TryReadString(initial.Attributes,             attrPath, "before-rt");
        Console.WriteLine($"[probe] BEFORE cfg={Quote(beforeCfg)} rt={Quote(beforeRt)}");

        // Mutate via the canonical TN-537 sequence on the same proxy.
        Console.WriteLine("[probe] CheckOut...");
        initial.CheckOut();
        ThrowIfFailed(initial.CommandResult, "CheckOut");

        Console.WriteLine("[probe] ConfigurableAttributes[...].SetValue(MxValue)...");
        var cfgAttr = initial.ConfigurableAttributes[attrPath];
        var v = new MxValueClass();
        v.PutString(Marker);
        cfgAttr.SetValue(v);

        Console.WriteLine("[probe] Save...");
        initial.Save();
        ThrowIfFailed(initial.CommandResult, "Save");

        Console.WriteLine("[probe] CheckIn...");
        initial.CheckIn("graccesscli probe round-trip test");
        ThrowIfFailed(initial.CommandResult, "CheckIn");

        // Now read back THREE ways:
        //   A. Same proxy (initial), ConfigurableAttributes — possibly stale.
        //   B. Same proxy (initial), Attributes — possibly stale.
        //   C. Fresh proxy from a fresh QueryObjectsByName call.
        var afterCfgSame = TryReadString(initial.ConfigurableAttributes, attrPath, "after-cfg-same");
        var afterRtSame  = TryReadString(initial.Attributes,             attrPath, "after-rt-same");
        Console.WriteLine($"[probe] AFTER (same proxy) cfg={Quote(afterCfgSame)} rt={Quote(afterRtSame)}");

        var refreshed = QueryOne(galaxy, names);
        var afterCfgFresh = TryReadString(refreshed.ConfigurableAttributes, attrPath, "after-cfg-fresh");
        var afterRtFresh  = TryReadString(refreshed.Attributes,             attrPath, "after-rt-fresh");
        Console.WriteLine($"[probe] AFTER (fresh proxy) cfg={Quote(afterCfgFresh)} rt={Quote(afterRtFresh)}");

        // Restore: write the original value back (or empty if there was none).
        var restoreTo = beforeCfg ?? "";
        Console.WriteLine($"[probe] Restoring DeclarationsText to {Quote(restoreTo)}...");
        refreshed.CheckOut();
        ThrowIfFailed(refreshed.CommandResult, "CheckOut(restore)");
        var restoreAttr = refreshed.ConfigurableAttributes[attrPath];
        var rv = new MxValueClass();
        rv.PutString(restoreTo);
        restoreAttr.SetValue(rv);
        refreshed.Save();
        ThrowIfFailed(refreshed.CommandResult, "Save(restore)");
        refreshed.CheckIn("graccesscli probe restore");
        ThrowIfFailed(refreshed.CommandResult, "CheckIn(restore)");
        Console.WriteLine("[probe] Restored.");

        // Verdict.
        Console.WriteLine();
        Console.WriteLine("=== verdict ===");
        Console.WriteLine($"  marker landed on same-proxy   ConfigurableAttributes: {(afterCfgSame == Marker)}");
        Console.WriteLine($"  marker landed on same-proxy   Attributes            : {(afterRtSame  == Marker)}");
        Console.WriteLine($"  marker landed on fresh-proxy  ConfigurableAttributes: {(afterCfgFresh == Marker)}");
        Console.WriteLine($"  marker landed on fresh-proxy  Attributes            : {(afterRtFresh  == Marker)}");

        return 0;
    }

    static IgObject QueryOne(IGalaxy galaxy, string[] names)
    {
        var objs = galaxy.QueryObjectsByName(EgObjectIsTemplateOrInstance.gObjectIsTemplate, ref names);
        ThrowIfFailed(galaxy.CommandResult, "QueryObjectsByName");
        return objs[1];
    }

    static string TryReadString(IAttributes attrs, string name, string label)
    {
        try
        {
            var attr = attrs[name];
            if (attr == null) return null;
            var val = attr.value;
            return val?.GetString();
        }
        catch (Exception ex) { Console.WriteLine($"[probe] {label} read threw: {ex.Message}"); return null; }
    }

    static void ThrowIfFailed(ICommandResult r, string what)
    {
        if (r != null && !r.Successful)
            throw new InvalidOperationException($"{what} failed: ID={r.ID} Text='{r.Text}' Custom='{r.CustomMessage}'");
    }

    static string Quote(string s) => s == null ? "<null>" : "\"" + s.Replace("\n", "\\n").Replace("\r", "\\r") + "\"";
}
