/**
 * @module sync-withid.establish.respec
 *
 * Phase 1 — `establishSessionIdentity` (Pre-Connect)
 *
 * Goal: Get `I^Itjp` onto the domain provider, generate `S^Stjp` locally,
 * evolve `I → I1` with a `sync` claim targeting `S^Stjp`, and post both
 * `I1` and `S` to the provider. Verify both keystones are in the appropriate
 * durable spaces at the right times.
 *
 * `senderCoordinator.sync(...)` IS called — we are not mocking. We expect it
 * may throw at first. We examine side-effects (keystone presence in durable
 * spaces) rather than end-to-end correctness. As phases succeed and sync no
 * longer throws, assertions will be adjusted accordingly.
 *
 * @see libs/core-gib/src/sync/docs/security.md — Implementation Plan, Phase 1A
 */
import { respecfully, iReckon, ifWe } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
const maam = `[${import.meta.url}]`, sir = maam;
import { clone, delay, extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { getIbGibAddr } from '@ibgib/ts-gib/dist/helper.mjs';
import { GLOBAL_LOG_A_LOT } from '../core-constants.mjs';
import { SyncSagaCoordinator } from './sync-saga-coordinator.mjs';
import { Metaspace_Innerspace } from '../witness/space/metaspace/metaspace-innerspace/metaspace-innerspace.mjs';
import { InnerSpace_V1 } from '../witness/space/inner-space/inner-space-v1.mjs';
import { SyncPeerInnerspace_V1 } from './sync-peer/sync-peer-innerspace/sync-peer-innerspace-v1.mjs';
import { DEFAULT_INNER_SPACE_DATA_V1 } from '../witness/space/inner-space/inner-space-types.mjs';
import { SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1 } from './sync-peer/sync-peer-innerspace/sync-peer-innerspace-constants.mjs';
import { KeystoneService_V1 } from '../keystone/keystone-service-v1.mjs';
import { KEYSTONE_VERB_SYNC, POOL_ID_SYNC, POOL_ID_CONNECT, KEYSTONE_VERB_CONNECT, } from '../keystone/keystone-constants.mjs';
import { createStandardPoolConfig } from '../keystone/keystone-config-builder.mjs';
import { KeystoneReplenishStrategy } from '../keystone/keystone-types.mjs';
import { SyncConflictStrategy } from './sync-constants.mjs';
import { getIdentity_throwIfUndefined } from '../keystone/keystone-helpers.mjs';
import { Factory_V1 } from '@ibgib/ts-gib/dist/V1/factory.mjs';
import { ROOT } from '@ibgib/ts-gib/dist/V1/constants.mjs';
import { fork } from '@ibgib/ts-gib/dist/V1/transforms/fork.mjs';
const logalot = GLOBAL_LOG_A_LOT;
const lc = sir;
// ---------------------------------------------------------------------------
// Test-only identity constants
// ---------------------------------------------------------------------------
/**
 * Plaintext secret for the senderIdentity keystone (I).
 * Test-only — never use plaintext secrets in production.
 */
const SENDER_SECRET = 'test-sender-secret-phase1';
// ---------------------------------------------------------------------------
// Session keystone pool configs
// ---------------------------------------------------------------------------
/**
 * `connect` pool — used once during `peer.connect()` transport handshake.
 * Small pool; fully consumed in one handshake.
 */
const SESSION_CONNECT_POOL_CONFIG = createStandardPoolConfig({
    id: POOL_ID_CONNECT,
    salt: 'session-connect-salt-phase1',
    verbs: [KEYSTONE_VERB_CONNECT],
    // Small size: fully consumed in a single connect handshake
    size: 10,
    sequential: 1,
    random: 1,
    targetBinding: 2,
    replenishStrategy: KeystoneReplenishStrategy.deleteAll,
});
/**
 * `sync` pool — used per outgoing context frame (Init, Delta, Commit).
 * Replenishes via topUp to stay active throughout the saga.
 */
const SESSION_SYNC_POOL_CONFIG = createStandardPoolConfig({
    id: POOL_ID_SYNC,
    salt: 'session-sync-salt-phase1',
    verbs: [KEYSTONE_VERB_SYNC],
    size: 200,
    sequential: 1,
    random: 1,
    targetBinding: 2,
    replenishStrategy: KeystoneReplenishStrategy.topUp,
});
// ---------------------------------------------------------------------------
// Top-level senderIdentity (I) pool config
// ---------------------------------------------------------------------------
/**
 * The senderIdentity needs a `sync` pool so it can evolve itself (I → I1)
 * with a claim targeting the session keystone genesis (S^Stjp).
 */
const SENDER_IDENTITY_SYNC_POOL_CONFIG = createStandardPoolConfig({
    id: POOL_ID_SYNC,
    salt: 'senderidentitysyncsaltphase1', // dashes not allowed in regex
    verbs: [KEYSTONE_VERB_SYNC],
    size: 200,
    sequential: 1,
    random: 1,
    targetBinding: 2,
    replenishStrategy: KeystoneReplenishStrategy.topUp,
});
// ---------------------------------------------------------------------------
// Main test suite
// ---------------------------------------------------------------------------
await respecfully(sir, `Test Phase 1: establishSessionIdentity`, async () => {
    // #region Init/Setup
    const metaspace = new Metaspace_Innerspace(undefined);
    await metaspace.initialize({
        getFnAlert: () => async ({ title, msg }) => { console.log(`[Alert] ${title}: ${msg}`); },
        getFnPrompt: () => async ({ title, msg }) => { console.log(`[Prompt] ${title}: ${msg}`); return ''; },
        getFnPromptPassword: () => async (title, msg) => { console.log(`[PromptPwd] ${title}: ${msg}`); return null; },
    });
    while (!metaspace.initialized) {
        await delay(10);
    }
    const defaultLocalUserSpace = await metaspace.getLocalUserSpace({ lock: false });
    await defaultLocalUserSpace.initialized;
    /** Sender's durable space — where the sender originates ibgibs. */
    const sourceSpace = new InnerSpace_V1({
        ...DEFAULT_INNER_SPACE_DATA_V1,
        name: 'source',
        uuid: 'source_uuid',
        description: 'sender durable space',
    });
    await sourceSpace.initialized;
    /** Receiver's durable space — the domain provider in the innerspace simulation. */
    const destSpace = new InnerSpace_V1({
        ...DEFAULT_INNER_SPACE_DATA_V1,
        name: 'dest',
        uuid: 'dest_uuid',
        description: 'receiver (domain provider) durable space',
    });
    await destSpace.initialized;
    const senderCoordinator = new SyncSagaCoordinator();
    const receiverCoordinator = new SyncSagaCoordinator();
    async function newTestIbGib_stone({ ib = 'test', data }) {
        const stone = await Factory_V1.stone({
            parentPrimitiveIb: ib.split(' ').at(0) ?? 'test',
            ib,
            data,
            uuid: true,
        });
        return stone;
    }
    async function newTestIbGib({ ib = 'test' }) {
        let resFork = await fork({
            src: ROOT,
            destIb: ib,
            tjp: { timestamp: true, uuid: true },
            dna: true,
            nCounter: true,
        });
        return resFork;
    }
    async function newTestPeer() {
        const peer = new SyncPeerInnerspace_V1(clone(SYNC_PEER_INNERSPACE_DEFAULT_DATA_V1));
        await peer.initialized;
        await peer.initializeOpts({
            sagaId: '', // coordinator will override this via setOptionalOpts before establishSessionIdentity
            localMetaspace: metaspace,
            localSpace: sourceSpace,
            receiverSpace: destSpace,
            receiverCoordinator,
            receiverMetaspace: metaspace,
        });
        return peer;
    }
    // KeystoneService_V1 is stateless — new it inline wherever needed.
    const keystoneSvc = new KeystoneService_V1();
    // #endregion Init/Setup
    /**
     * senderIdentity (I): Alice's long-lived Domain Keystone.
     * Created fresh for this test with a `sync` pool so it can evolve (I → I1).
     */
    let senderIdentity;
    // #region Step 1: Prepare identity
    // Create senderIdentity genesis (I^Itjp) in sourceSpace
    senderIdentity = await keystoneSvc.genesis({
        masterSecret: SENDER_SECRET,
        configs: [SENDER_IDENTITY_SYNC_POOL_CONFIG],
        metaspace,
        space: sourceSpace,
    });
    if (logalot) {
        console.log(`${lc} senderIdentity genesis addr: ${getIbGibAddr({ ibGib: senderIdentity })}`);
    }
    // post the senderIdentity to receiver (like "create account")
    await metaspace.put({ ibGib: senderIdentity, space: destSpace });
    await metaspace.registerNewIbGib({ ibGib: senderIdentity, space: destSpace });
    // #endregion Step 1: Prepare identity
    // at this point, we have mimicked a user who already has an identity via a
    // create account button.
    // #region Step 2: Execute
    let xStone;
    let xStoneAddr;
    try {
        if (logalot) {
            console.log(`${lc}[Step 2] starting... (I: 1aa5643dd8d46fcdd87d48b8be550826)`);
        }
        // setup/call sync
        // Sync call — sync itself is responsible for establishSessionIdentity
        // internally.
        /**
         * doesn't really matter intrinsically for this test, but we need a
         * domain ibgib to sync
         */
        xStone = await newTestIbGib_stone({ ib: 'test' });
        xStoneAddr = getIbGibAddr({ ibGib: xStone });
        await metaspace.put({ ibGib: xStone, space: sourceSpace });
        await metaspace.registerNewIbGib({ ibGib: xStone, space: sourceSpace });
        const syncSaga = await senderCoordinator.sync({
            domainIbGibs: [xStone],
            senderIdentity,
            fnSenderSecret: async () => { return SENDER_SECRET; },
            peer: await newTestPeer(),
            localSpace: sourceSpace,
            metaspace,
            conflictStrategy: SyncConflictStrategy.optimisticWithLCS,
        });
        await syncSaga.done;
    }
    catch (error) {
        // error is fine/expected right now. later perhaps we will throw, or
        // perhaps we always just swallow any exceptions since this is close to
        // a unit test
        console.error(`${lc} ${extractErrorMsg(error)}`);
    }
    finally {
        if (logalot) {
            console.log(`${lc}[Step 2] complete.`);
        }
    }
    // #endregion Step 2: Execute
    // #region Step 3: Check states
    // check state of sender/receiver spaces WRT sender identity, new sender
    // identity, and session identity.
    // get newSenderIdentity address via metaspace.getLatestAddr
    // Resolve the latest senderIdentity frame (I1) from the sender's space.
    // After sync, the coordinator should have evolved I → I1 and stored it.
    let newSenderIdentityAddr;
    /**
     * The session keystone addr should be embedded in I1's proof claim target.
     */
    let newSenderIdentity;
    await ifWe(sir, 'newSenderIdentity created and stored in source space', async () => {
        newSenderIdentityAddr = await metaspace.getLatestAddr({
            ibGib: senderIdentity,
            space: sourceSpace,
        });
        if (!newSenderIdentityAddr) {
            throw new Error(`newSenderIdentity not found in space (${sourceSpace.ib}). this should have been evolved and stored during sync (E: a5a798bf8ba467cbc87595dcc2b36726)`);
        }
        newSenderIdentity = await getIdentity_throwIfUndefined({
            addr: newSenderIdentityAddr,
            metaspace,
            space: sourceSpace,
        });
        // todo: add iReckon statements for expectations like claim verb, claim target, etc., of newSenderIdentity
        iReckon(sir, newSenderIdentity).asTo('newSenderIdentity is truthy').isGonnaBeTruthy();
        const syncProof = newSenderIdentity?.data?.proofs?.find(p => p.claim?.verb === KEYSTONE_VERB_SYNC);
        iReckon(sir, syncProof).asTo('I1 has a sync-verb proof/claim').isGonnaBeTruthy();
        iReckon(sir, syncProof?.claim?.target).asTo('sync claim has a target (S^Stjp)').isGonnaBeTruthy();
    });
    if (!newSenderIdentity) {
        throw new Error(`(UNEXPECTED) newSenderIdentity falsy? should have thrown before this if falsy. (E: 7a3d92e6160409de149eaf6802365126)`);
    }
    const sessionIdentityTjpAddr = newSenderIdentity.data.proofs
        .find(p => p.claim.verb === KEYSTONE_VERB_SYNC)?.claim.target;
    if (!sessionIdentityTjpAddr) {
        throw new Error(`(UNEXPECTED) sessionIdentityTjpAddr falsy? (E: c53583b07a78837de84a59388b6ff826)`);
    }
    let sessionIdentity;
    await ifWe(sir, 'creates sessionIdentity genesis (S) locally — exists in sourceSpace', async () => {
        sessionIdentity = await getIdentity_throwIfUndefined({
            addr: sessionIdentityTjpAddr,
            metaspace,
            space: sourceSpace,
        });
        iReckon(sir, sessionIdentity).asTo('sessionIdentity is truthy').isGonnaBeTruthy();
    });
    if (!sessionIdentity) {
        throw new Error(`(UNEXPECTED) sessionIdentity falsy? (E: e1fa06009df535f3c848e6ca8b0bd326)`);
    }
    await ifWe(sir, 'Session identity S has expected state', async () => {
        // #region sanity/compile
        if (!sessionIdentity) {
            throw new Error(`(UNEXPECTED) sessionIdentity falsy? (E: dd229f4e44489a54488768157a393926)`);
        }
        if (!sessionIdentity.data) {
            throw new Error(`(UNEXPECTED) sessionIdentity.data falsy? (E: 56f10c9f9c18c6147ea19281dcbaf826)`);
        }
        // #endregion sanity/compile
        // Verify S has both pool ids: connect and sync
        const sPools = sessionIdentity.data.challengePools;
        iReckon(sir, sPools).asTo('S has challengePools').isGonnaBeTruthy();
        const hasConnectPool = sPools?.some(p => p.id === POOL_ID_CONNECT);
        const hasSyncPool = sPools?.some(p => p.id === POOL_ID_SYNC);
        iReckon(sir, hasConnectPool).asTo('S has connect pool').isGonnaBeTrue();
        iReckon(sir, hasSyncPool).asTo('S has sync pool').isGonnaBeTrue();
        const sProofs = sessionIdentity.data.proofs;
        iReckon(sir, sProofs).asTo('S has proofs array').isGonnaBeTruthy();
        iReckon(sir, sProofs.length === 0).asTo('S has 0 proofs on genesis').isGonnaBeTrue();
        // Verify S is bound to target domain (xStone) via frameDetails
        const targetAddrs = sessionIdentity.data.frameDetails?.targetAddrs;
        iReckon(sir, targetAddrs).asTo('S has targetAddrs array in frameDetails').isGonnaBeTruthy();
        iReckon(sir, targetAddrs?.includes(xStoneAddr)).asTo('S targetAddrs contains the domain being synced (xStoneAddr)').isGonnaBeTrue();
    });
    await ifWe(sir, 'I, I1 and S all exist in destSpace (receiver)', async () => {
        // todo: use getIdentity_throwIfUndefined for all three identities but in destSpace
        const senderIdentityAddr = getIbGibAddr({ ibGib: senderIdentity });
        await getIdentity_throwIfUndefined({
            addr: senderIdentityAddr,
            metaspace,
            space: destSpace
        });
        iReckon(sir, true).asTo('I (original) exists in destSpace').isGonnaBeTrue();
        if (!newSenderIdentityAddr) {
            throw new Error(`newSenderIdentity not found in space (${sourceSpace.ib}). this should have been evolved and stored during sync (E: b626885071885b38d87853189f25c826)`);
        }
        await getIdentity_throwIfUndefined({ addr: newSenderIdentityAddr, metaspace, space: destSpace });
        iReckon(sir, true).asTo('I1 (evolved) exists in destSpace').isGonnaBeTrue();
        await getIdentity_throwIfUndefined({ addr: sessionIdentityTjpAddr, metaspace, space: destSpace });
        iReckon(sir, true).asTo('S (sessionIdentity) exists in destSpace').isGonnaBeTrue();
    });
    // #endregion Step 3: Check states
});
//# sourceMappingURL=sync-withid.establish.respec.mjs.map