/**
 * @module sync saga context helpers
 */
import { extractErrorMsg, pretty, } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { getIbGibAddr, } from '@ibgib/ts-gib/dist/helper.mjs';
import { validateIbGibIntrinsically } from '@ibgib/ts-gib/dist/V1/validate-helper.mjs';
import { GLOBAL_LOG_A_LOT } from '../../core-constants.mjs';
import { SYNC_SAGA_CONTEXT_ATOM } from './sync-saga-context-constants.mjs';
import { SYNC_MSG_REL8N_NAME } from '../sync-constants.mjs';
import { getFromSpace, getLatestAddrs, getTjpIbGib } from '../../witness/space/space-helper.mjs';
import { validateSyncSagaFrame } from '../sync-helpers.mjs';
import { isIbGibWithAtom, toDto } from '../../common/other/ibgib-helper.mjs';
import { KeystoneService_V1 } from '../../keystone/keystone-service-v1.mjs';
import { KEYSTONE_VERB_SYNC } from '../../keystone/keystone-constants.mjs';
const logalot = GLOBAL_LOG_A_LOT;
/**
 * Constructs the standard 'ib' string for a Sync Saga Context stone.
 */
export async function getSyncSagaContextIb({ data, }) {
    const lc = `[${getSyncSagaContextIb.name}]`;
    try {
        if (!data) {
            throw new Error(`data required (E: 839d82138c234b3e89582855594191fe)`);
        }
        const payloadCount = data['@payloadAddrsDomain'] ? data['@payloadAddrsDomain'].length : 0;
        const ib = [
            SYNC_SAGA_CONTEXT_ATOM,
            data.sagaN,
            payloadCount,
        ].join(' ');
        return ib;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
}
/**
 * Parses a standard Sync Saga Context 'ib' string.
 */
export async function parseSyncSagaContextIb({ ib, }) {
    const lc = `[${parseSyncSagaContextIb.name}]`;
    try {
        const parts = ib.split(' ');
        if (parts.length !== 2) {
            throw new Error(`Invalid sync saga context ib. Expected at least 2 parts [atom, payloadCount]. Got ${parts.length}. (E: 8b06c88820c74f5fb9f21f148459422a)`);
        }
        const [atom, sagaNStr, payloadCountStr,] = parts;
        if (atom !== SYNC_SAGA_CONTEXT_ATOM) {
            throw new Error(`Atom mismatch. Expected ${SYNC_SAGA_CONTEXT_ATOM}. Got ${atom}. (E: 67d71fc0f8d04840b178652309f471f5)`);
        }
        if (!sagaNStr) {
            throw new Error(`invalid ib. second piece should be sagaN but is falsy (E: 081ed475aeb8fe25a66f60466b2e5826)`);
        }
        const sagaN = parseInt(sagaNStr);
        if (isNaN(sagaN)) {
            throw new Error(`sagaNStr is not a number. (E: 3d6b580d5a48c5e03ecec288896e2826)`);
        }
        if (!payloadCountStr) {
            throw new Error(`invalid ib. second piece should be payloadCount but is falsy (E: 0bb7580793d800ca02227828a7294826)`);
        }
        const payloadCount = parseInt(payloadCountStr);
        if (isNaN(payloadCount)) {
            throw new Error(`payloadCountStr is not a number. (E: 49457814282a44069871630987588722)`);
        }
        return { atom, sagaN, payloadCount };
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
}
export function isSyncSagaContextIbGib(x) {
    const lc = `[${isSyncSagaContextIbGib.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 2bfa7f3cf5984b0c78aced881ce95226)`);
        }
        const hasAtom = isIbGibWithAtom(x, SYNC_SAGA_CONTEXT_ATOM);
        // should be good enough in V1
        return hasAtom;
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * Validates ONLY the {@link context} ibgib itself and saga frame/msg stone(s)
 * ({@link SyncSagaContextIbGib_V1.sagaFrame}).
 *
 * Does NOT...
 * * validate authn or authz
 * * validate domain payload ibgibs...
 *   * intrinsically
 *   * in relation to this context (addrs match up)
 *
 * @returns empty array if valid, else validation errors
 */
export async function validateContextAndSagaFrame({ context, }) {
    const lc = `[${validateContextAndSagaFrame.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 7797f8294bd8f7e5089cb722ad468226)`);
        }
        const errors = [];
        // validate just the context first. if this fails we return early.
        const intrinsicErrors = await validateIbGibIntrinsically({ ibGib: context });
        if (intrinsicErrors && intrinsicErrors.length > 0) {
            intrinsicErrors.forEach(x => errors.push(x));
            return errors; /* <<<< returns early */
        }
        if (context.sagaFrame) {
            const sagaFrameErrors = await validateSyncSagaFrame({ sagaIbGib: context.sagaFrame });
            sagaFrameErrors.forEach(x => errors.push(x));
        }
        else {
            errors.push(`context.sagaFrame is falsy. (E: b4edd88f4963f493789f83b29ba2df26)`);
        }
        if (context.sagaFrameMsg) {
            const sagaFrameMsgErrors = await validateIbGibIntrinsically({ ibGib: context.sagaFrameMsg }) ?? [];
            sagaFrameMsgErrors.forEach(x => errors.push(x));
            if (context.sagaFrame) {
                const expectedMsgAddr = context.sagaFrame.rel8ns?.[SYNC_MSG_REL8N_NAME]?.[0];
                if (expectedMsgAddr) {
                    const actualMsgAddr = getIbGibAddr({ ibGib: context.sagaFrameMsg });
                    if (actualMsgAddr !== expectedMsgAddr) {
                        errors.push(`context.sagaFrameMsg address (${actualMsgAddr}) does not match the stone address referenced in sagaFrame relations (${expectedMsgAddr}). (E: a983b271fcae46bbad7e82098bc24826)`);
                    }
                }
                else {
                    errors.push(`context.sagaFrame is missing the message stone relation '${SYNC_MSG_REL8N_NAME}'. (E: da872cf3a8d46dbbad89d0a68d712826)`);
                }
            }
        }
        else {
            errors.push(`context.sagaFrameMsg is falsy. (E: ed405a72ab0d8bbdca7b9605d8f9a26)`);
        }
        // if this is already invalid, we could have intrinsic validation
        // errors, which are a non-starter.
        if (errors.length > 0) {
            return errors; /* <<<< returns early */
        }
        // ~~ensure the non-dto properties on the context object (i.e. props
        // that are not ib, gib, data, or rel8ns) match the data in the
        // context object~~ NO. THIS WILL BE IN A SEPARATE FUNCTION.
        // See `validateContextDomainPayloadIbGibs`
        // throw new Error(`not implemented (E: cb639658e1183fa6c8a4ebca023ba926)`);
        console.error(`${lc} NAG ERROR MSG (NOT THROWN): not fully implemented yet (E: d574d85cb578637648ed403857f7a826)`);
        return [];
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * validates {@link SyncSagaContextIbGib_V1.payloadIbGibsDomain} intrinsically,
 * and ensures that these payloadIbGibs also match the expected addrs in
 * {@link SyncSagaContextData_V1}[{@link SYNC_SAGA_PAYLOAD_ADDRS_DOMAIN}].
 *
 * @returns empty array if valid, else validation errors
 */
export async function validateContextDomainPayloadIbGibs({ context }) {
    const lc = `[${validateContextDomainPayloadIbGibs.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 9f9fe835bcc80c75aa7fd6d887fc0826)`);
        }
        console.error(`${lc} NAG MESSAGE (NOT THROWN). not implemented (E: bfe02ab36a227291a8f17ffc08f2ef26)`);
        return [];
    }
    catch (error) {
        console.error(`${lc} ${extractErrorMsg(error)}`);
        throw error;
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
/**
 * "Intrinsically": This authenticates assuming we have already established that
 * this context is a valid continuation of previous sync contexts/saga state. It
 * does not check that the session identity contained on this context is a valid
 * continuation.
 *
 * ## notes
 *
 * Say an attacker tries to hijack a legit sync process by capturing previous
 * context(s). Then the attacker just either creates its own sessionIdentity
 *
 * ## implementation notes
 *
 * This is a HUGE function right now, because there are just a lot of things to
 * check. I'm basically going through and just taking every assumption that I
 * can think of and encoding it.
 *
 * We will need to refactor this at some point to neaten it up, but we should
 * not remove sections without EXTREMELY good reasoning, as this would reduce
 * security.
 */
export async function authenticateContextIntrinsically({ context, space,
// stageInProtocol,
 }) {
    const lc = `[${authenticateContextIntrinsically.name}]`;
    try {
        if (logalot) {
            console.log(`${lc} starting... (I: 3c34e8f1d6ef965f98725c88459ea926)`);
        }
        const errors = [];
        if (!context.data) {
            throw new Error(`(UNEXPECTED) context.data falsy? (E: 3e4ddd0eb4b828ad489658d88d9a6326)`);
        }
        if (!context.rel8ns) {
            throw new Error(`(UNEXPECTED) context.rel8ns falsy? (E: 8026589d4fed69c828334ee842074326)`);
        }
        const { sagaFrame, signedSessionIdentity: currSessionIdentity } = context;
        if (!sagaFrame.data) {
            throw new Error(`(UNEXPECTED) sagaFrame.data falsy? (E: b61cc82d25984c92f75db74a5a855b26)`);
        }
        // We only sign at the context level.
        // If context has no signedSessionIdentity, skip authentication (anonymous or broker response).
        if (!currSessionIdentity) {
            // check the sync saga to determine if there _should_ be a session
            // identity according to the given sagaFrame (which could be
            // malicious, remember!!)
            // todo: add logic to SET this property when using identity, then (and only then) remove this todo.
            console.error(`${lc}[NAG][not thrown] sagaFrame.data.sessionIdentityTjpAddr logic needs to be added in coordinator (E: 4fc47800a1086c917a47381824280826)`);
            if (sagaFrame.data.sessionIdentityTjpAddr) {
                errors.push('Context has no session identity, but sync saga frame shows a session identity (sagaFrame.data.sessionIdentityTjpAddr is truthy). (E: 69dd6cdc2e1859c0f3d62958c4339826)');
                return errors; /* <<<< returns early */
            }
            else if (context.rel8ns.sessionIdentity) {
                errors.push('Context has no signed session identity, but context.rel8ns.sessionIdentity is truthy. (E: 96a04a8a6c88ea8bf88118f89ad8e326)');
                return errors; /* <<<< returns early */
            }
            else {
                // nothing further to authenticate
                if (logalot) {
                    console.log(`${lc} context has no signedSessionIdentity and sync saga frame doesn't state there should be session identity. So nothing further to authenticate - returning early with no authentication errors. (I: d708735f9a2899ee98f762b8a09ed826)`);
                }
                return []; /* <<<< returns early */
            }
        }
        const currSessionIdentityAddr = getIbGibAddr({ ibGib: currSessionIdentity });
        // ensure the context rel8ns points to a session identity
        const prevSessionIdentityAddrs_accordingToContextRel8ns = context.rel8ns?.sessionIdentity ?? [];
        if (prevSessionIdentityAddrs_accordingToContextRel8ns.length === 0) {
            errors.push(`context.rel8ns.sessionIdentity is falsy/empty but context.signedSessionIdentity is present. (E: 66f906421eb2468c0b33f908a3cf2826)`);
            return errors; /* <<<< returns early */
        }
        if (prevSessionIdentityAddrs_accordingToContextRel8ns.length > 1) {
            errors.push(`context.rel8ns.sessionIdentity has multiple identity addrs. (E: 489428bfe6fdaa4cd885b938dc4c5826)`);
            return errors; /* <<<< returns early */
        }
        // ensure the context session identity is the immediate past of the
        // current session identity
        const prevSessionIdentityAddr = prevSessionIdentityAddrs_accordingToContextRel8ns[0];
        // Confirm previous session identity addr exists in space and that it is
        // the most recent in the session keystone's timeline
        const resGetLatestAddr = await getLatestAddrs({ addrs: [prevSessionIdentityAddr], space });
        if (!resGetLatestAddr) {
            throw new Error(`(UNEXPECTED) resGetLatestAddr for prevSessionIdentityAddr in space (${space.ib}) falsy? (E: 7b207e5cbcec9037ea5adbe822ead826)`);
        }
        if (!resGetLatestAddr.data) {
            throw new Error(`(UNEXPECTED) resGetLatestAddr.data for prevSessionIdentityAddr in space (${space.ib}) falsy? (E: de4eb8d730c8c4dcb59c8b9c79277826)`);
        }
        if (!resGetLatestAddr.data.success) {
            throw new Error(`(UNEXPECTED) resGetLatestAddr.data.success falsy? (E: c94298dfd9684ad6a87eb748459aa826)`);
        }
        const { latestAddrsMap } = resGetLatestAddr.data;
        if (!latestAddrsMap) {
            throw new Error(`(UNEXPECTED) resGetLatestAddr.data.latestAddrsMap falsy? (E: 19f1fd5fe798cf2e5fa923919169d826)`);
        }
        if (Object.keys(latestAddrsMap).length !== 1) {
            throw new Error(`(UNEXPECTED) Object.keys(latestAddrsMap).length !== 1? (E: fe526a0747589c6427a8bcc86da34a26)`);
        }
        const prevSessionIdentityAddr_latest = latestAddrsMap[prevSessionIdentityAddr];
        if (!prevSessionIdentityAddr_latest) {
            errors.push(`prevSessionIdentityAddr (${prevSessionIdentityAddr}) not found in space (${space.ib}). this should have been the incoming prevSessionIdentityAddr (E: f6d042bd6b54819998653228dee34226)`);
            return errors; /* <<<< returns early */
        }
        if (prevSessionIdentityAddr !== prevSessionIdentityAddr_latest) {
            if (prevSessionIdentityAddr_latest === currSessionIdentityAddr) {
                // this is ok? if the sender peer is calling this just to validate **before sending**, then this will be the case. If the receiver is calling this code before continuing the sync/at the start of continuing the sync, then this will not hit.
                // debugger; // in sync saga context auth, want to know if this hits...this does hit, so my thoughts on the innerspace/sender peer seem to be correct
            }
            else {
                // debugger; // in sync saga context auth, want to know if this hits...so far this does NOT hit
                errors.push(`context.rel8ns.sessionIdentity does not point to the most recent in the space (${space.ib}). (E: 2f8288f53c87b6aa47bd2178d9df0c26)`);
                // #region debug error keystone
                console.log(`context: ${pretty(toDto({ ibGib: context }))}`);
                console.log(`prevSessionIdentityAddr (context.rel8ns.sessionIdentity): ${prevSessionIdentityAddr}`);
                console.log(`prevSessionIdentityAddr_latest: ${prevSessionIdentityAddr_latest}`);
                console.log(`currSessionIdentity (context.signedSessionIdentity): ${pretty(toDto({
                    ibGib: {
                        ib: currSessionIdentity.ib,
                        gib: currSessionIdentity.gib,
                        rel8ns: currSessionIdentity.rel8ns,
                        data: {
                            ...currSessionIdentity.data,
                            challengePools: currSessionIdentity.data.challengePools.map(p => {
                                return { ...p, challenges: {} };
                            }),
                        },
                    }
                }))}`);
                console.log(`currSessionIdentityAddr: ${currSessionIdentityAddr}`);
                // #endregion debug error keystone
                return errors; /* <<<< returns early */
            }
        }
        const resGetPrevSessionIdentity = await getFromSpace({ addr: prevSessionIdentityAddr, space });
        if (!resGetPrevSessionIdentity.success || resGetPrevSessionIdentity.ibGibs?.length !== 1) {
            errors.push(`could not fetch latest sender identity ${prevSessionIdentityAddr} from space (${space.ib}). (E: fd48c3e64c9fa4efd8a1f8280af18226)`);
            return errors;
        }
        const prevSessionIdentity = resGetPrevSessionIdentity.ibGibs[0];
        // get the session identity tjp, which has frame details that link back
        // to the identity that authorized the session
        const sessionIdentityTjp = await getTjpIbGib({ ibGib: prevSessionIdentity, naive: true, space });
        if (!sessionIdentityTjp) {
            throw new Error(`(UNEXPECTED) couldn't get sessionIdentityTjp in space (${space.ib})? we have already gotten the identity itself in the space, so we would expect the entire timeline to exist in it. (E: 9be0382ff1c8a0e77645ea38c096f826)`);
        }
        const sessionIdentityTjpAddr = getIbGibAddr({ ibGib: sessionIdentityTjp });
        if (sessionIdentityTjpAddr !== sagaFrame.data.sessionIdentityTjpAddr) {
            throw new Error(`(UNEXPECTED) sessionIdentityTjpAddr !== sagaFrame.data.sessionIdentityTjpAddr? (E: c9a4ad5c2728fe38e86afc58e4abaf26)`);
        }
        const sessionGenesisFrameDetails = sessionIdentityTjp.data.frameDetails;
        if (!sessionGenesisFrameDetails) {
            errors.push(`Invalid session identity tjp: sessionIdentityTjp.data.frameDetails is falsy. (E: 0187f8f804a84256281720586620b826)`);
            return errors; /* <<<< returns early */
        }
        const { senderIdentityAddr, senderIdentityTjpAddr } = sessionGenesisFrameDetails;
        if (!senderIdentityAddr) {
            throw new Error(`sessionGenesisFrameDetails.senderIdentityAddr falsy (E: 02a0c80a3ead9e3af8af4cf3b156e826)`);
        }
        if (!senderIdentityTjpAddr) {
            throw new Error(`sessionGenesisFrameDetails.senderIdentityTjpAddr falsy (E: 271928090ff5dc56d4bb63d8d5c68826)`);
        }
        const resGetLatestAddr_senderIdentity = await getLatestAddrs({ addrs: [senderIdentityTjpAddr, senderIdentityAddr], space });
        if (!resGetLatestAddr_senderIdentity) {
            throw new Error(`(UNEXPECTED) resGetLatestAddr_senderIdentity for prevSessionIdentityAddr in space (${space.ib}) falsy? (E: 2e4ae8083b6fb7cbb8fae2a519062926)`);
        }
        if (!resGetLatestAddr_senderIdentity.data) {
            throw new Error(`(UNEXPECTED) resGetLatestAddr_senderIdentity.data for prevSessionIdentityAddr in space (${space.ib}) falsy? (E: 2e231850c2a898cc282b4b2841056826)`);
        }
        if (!resGetLatestAddr_senderIdentity.data.success) {
            throw new Error(`(UNEXPECTED) resGetLatestAddr_senderIdentity.data.success falsy? (E: e93508f03e0475925875b00746ffd826)`);
        }
        const { latestAddrsMap: latestAddrsMap_senderIdentity } = resGetLatestAddr_senderIdentity.data;
        if (!latestAddrsMap_senderIdentity) {
            throw new Error(`(UNEXPECTED) resGetLatestAddr_senderIdentity.data.latestAddrsMap falsy? (E: 87a91e3f9968ad9ba79cdfe8cd878326)`);
        }
        if (Object.keys(latestAddrsMap_senderIdentity).length !== 2 && senderIdentityTjpAddr !== senderIdentityAddr) {
            throw new Error(`(UNEXPECTED) Object.keys(latestAddrsMap_senderIdentity).length !== 2 && senderIdentityTjpAddr !== senderIdentityAddr? (E: fe46bd584853d8e1e8e2d11f52012826)`);
        }
        // these two should be the same, we're just confirming that they're both
        // on the same timeline.
        const senderIdentityTjpAddr_latest = latestAddrsMap_senderIdentity[senderIdentityTjpAddr];
        const senderIdentityAddr_latest = latestAddrsMap_senderIdentity[senderIdentityAddr];
        if (!senderIdentityAddr_latest) {
            throw new Error(`(UNEXPECTED) senderIdentityAddr_latest falsy? (E: e151798ae2e9241578d09948937c4b26)`);
        }
        if (senderIdentityTjpAddr_latest !== senderIdentityAddr_latest) {
            throw new Error(`senderIdentityTjpAddr_latest !== senderIdentityAddr_latest (E: 52478a1053589e72665031a853cc1826)`);
        }
        // ATOW, we're only allowing a single sync to occur on an identity at
        // any given time (which makes sense). We also are assuming that the
        // sender identity is not doing anything ELSE at this time, which in
        // the (far) future may change. So the user couldn't edit their primary
        // identity's profile, description, etc., while the sync is in progress.
        // This may ultimately be asking too much though. But for now, we'll
        // enforce that the latest senderIdentity addr should be that addr that
        // authorized the session keystone.
        // if (senderIdentityAddr_latest !== senderIdentityAddr) {
        //     errors.push(`The senderIdentityAddr referenced in the session keystone's genesis frameDetails (${senderIdentityAddr}) is DIFFERENT than the latest sender identity addr (${senderIdentityAddr_latest}). This means that the sender has done something besides the current sync operation, which isn't supported at this time. (E: a02598271b48cbeb584e45abde121826)`);
        //     return errors; /* <<<< returns early */
        // }
        // now we confirm the other direction: sender identity should have been
        // signed with "sync" verb and targeting the sessionIdentity tjp
        // (genesis) frame.
        const resGetSenderIdentity_latest = await getFromSpace({
            addr: senderIdentityAddr_latest,
            space,
        });
        if (!resGetSenderIdentity_latest.success || resGetSenderIdentity_latest.ibGibs?.length !== 1) {
            errors.push(`could not fetch latest sender identity ${prevSessionIdentityAddr} from space (${space.ib}). (E: 3565ff0ed458f5a2384c40b16e849826)`);
            return errors; /* <<<< returns early */
        }
        const senderIdentity_latest = resGetSenderIdentity_latest.ibGibs[0];
        if (!senderIdentity_latest.data.proofs) {
            errors.push(`Invalid sender identity. Proofs empty/falsy. (E: ebf488853061614d2b5b137828119526)`);
            return errors; /* <<<< returns early */
        }
        const syncClaim = senderIdentity_latest.data.proofs.find(p => p.claim.verb === KEYSTONE_VERB_SYNC)?.claim;
        if (!syncClaim) {
            errors.push(`Most recent senderIdentity has no proof whose claim.verb === ${KEYSTONE_VERB_SYNC}. (E: b0f488ecccbbfe43d9a0b7c8a29d7826)`);
            return errors; /* <<<< returns early */
        }
        if (syncClaim.target !== sessionIdentityTjpAddr) {
            errors.push(`Most recent sender identity claim has claim.verb === ${KEYSTONE_VERB_SYNC} but DOES NOT target expected session identity addr ${prevSessionIdentityAddr}. (E: 3e7f18d99848969be8586423d5ccb826)`);
            return errors;
        }
        const keystoneSvc = new KeystoneService_V1();
        const transitionErrors = await keystoneSvc.validate({
            currentIbGib: currSessionIdentity,
            prevIbGib: prevSessionIdentity,
        });
        if (transitionErrors.length > 0) {
            errors.push(`Invalid session identity transition: ${transitionErrors.join(', ')} (E: da1c81c6d3c86aec3254f48fe7514226)`);
        }
        // we have a valid keystone evolution/signing, but was it specifically
        // for this incoming context? verify that the signing targets context.
        const contextAddr = getIbGibAddr({ ibGib: context });
        const targetsThisContext = currSessionIdentity.data?.proofs?.some(p => p.claim.target === contextAddr);
        if (!targetsThisContext) {
            errors.push(`Session identity signature does not target current context ibgib (${contextAddr}). (E: acae68938c287178c878d1b88bebb826)`);
        }
        return errors;
    }
    catch (error) {
        const emsg = `${lc} ${extractErrorMsg(error)}`;
        console.error(emsg);
        return [`authentication produced an error: ${emsg} (E: 45e014b82af81993d936611ca6fc4d26)`];
    }
    finally {
        if (logalot) {
            console.log(`${lc} complete.`);
        }
    }
}
//# sourceMappingURL=sync-saga-context-helpers.mjs.map