import { __decorate, __metadata } from "tslib";
import { createHash } from 'node:crypto';
import { Injectable, Scope } from 'graphql-modules';
import { sql } from '@pgtyped/runtime';
import { DBProvider } from '../../app-providers/db.provider.js';
import { AuditLogsProvider } from '../../common/providers/audit-logs.provider.js';
import { alreadyAcceptedError, expiredTokenError, invalidTokenError, mapAuth0Error, } from '../helpers/invitations.helper.js';
import { Auth0ManagementProvider } from './auth0-management.provider.js';
const updateInvitationAcceptance = sql `
  UPDATE accounter_schema.invitations
  SET accepted_at = NOW()
  WHERE id = $id;
`;
const getInvitationForAcceptance = sql `
  SELECT id, user_id, business_id, role_id, email, auth0_user_id, accepted_at, expires_at
  FROM accounter_schema.invitations
  WHERE token_hash = $tokenHash
    AND accepted_at IS NULL
    AND expires_at > NOW()
  FOR UPDATE;
`;
const getInvitationByToken = sql `
  SELECT id, user_id, business_id, role_id, email, auth0_user_id, accepted_at, expires_at
  FROM accounter_schema.invitations
  WHERE token_hash = $tokenHash;
`;
const getUserIdByAuth0UserId = sql `
  SELECT user_id
  FROM accounter_schema.business_users
  WHERE auth0_user_id = $auth0UserId
  LIMIT 1;
`;
const insertAcceptedBusinessUser = sql `
  INSERT INTO accounter_schema.business_users (user_id, auth0_user_id, business_id, role_id)
  VALUES ($userId, $auth0UserId, $ownerId, $roleId);
`;
const updateBusinessUserAuth0Id = sql `
  UPDATE accounter_schema.business_users
  SET auth0_user_id = $auth0UserId, updated_at = NOW()
  WHERE user_id = $userId
    AND business_id = $ownerId;
`;
let AcceptInvitationsProvider = class AcceptInvitationsProvider {
    dbProvider;
    auth0ManagementProvider;
    auditLogsProvider;
    constructor(dbProvider, auth0ManagementProvider, auditLogsProvider) {
        this.dbProvider = dbProvider;
        this.auth0ManagementProvider = auth0ManagementProvider;
        this.auditLogsProvider = auditLogsProvider;
    }
    async acceptInvitation(token, auth0UserId, authenticatedUserEmail) {
        const tokenHash = createHash('sha256').update(token).digest('hex');
        const client = await this.dbProvider.pool.connect();
        try {
            await client.query('BEGIN');
            const activeInvitationResult = await getInvitationForAcceptance.run({ tokenHash }, client);
            if (activeInvitationResult.length === 0) {
                const invitationByTokenResult = await getInvitationByToken.run({ tokenHash }, client);
                if (invitationByTokenResult.length === 0) {
                    throw invalidTokenError();
                }
                const staleInvitation = invitationByTokenResult[0];
                if (staleInvitation.accepted_at) {
                    throw alreadyAcceptedError();
                }
                if (new Date(staleInvitation.expires_at).getTime() <= Date.now()) {
                    throw expiredTokenError();
                }
                throw invalidTokenError();
            }
            const invitation = activeInvitationResult[0];
            const effectiveAuth0UserId = auth0UserId ?? invitation.auth0_user_id;
            if (!effectiveAuth0UserId) {
                throw invalidTokenError();
            }
            // Defense in depth: authenticated users can claim only invitations for their own email.
            if (auth0UserId) {
                const normalizedInvitationEmail = invitation.email?.trim().toLowerCase();
                let normalizedAuthenticatedEmail = authenticatedUserEmail?.trim().toLowerCase() ?? null;
                // Access tokens for custom APIs often omit `email` claims.
                // In that case, resolve the primary email from Auth0 profile for comparison.
                if (!normalizedAuthenticatedEmail) {
                    const identity = await this.auth0ManagementProvider.getUserEmailById(auth0UserId);
                    if (identity?.emailVerified && identity.email) {
                        normalizedAuthenticatedEmail = identity.email.trim().toLowerCase();
                    }
                }
                if (!normalizedInvitationEmail ||
                    !normalizedAuthenticatedEmail ||
                    normalizedInvitationEmail !== normalizedAuthenticatedEmail) {
                    throw invalidTokenError();
                }
            }
            if (!invitation.user_id) {
                throw invalidTokenError();
            }
            const assignAuth0UserToInvitedUser = async () => {
                await updateBusinessUserAuth0Id.run({
                    auth0UserId: effectiveAuth0UserId,
                    userId: invitation.user_id,
                    ownerId: invitation.business_id,
                }, client);
            };
            let userId;
            if (auth0UserId) {
                const existingUserResult = await getUserIdByAuth0UserId.run({ auth0UserId }, client);
                if (existingUserResult.length > 0) {
                    userId = existingUserResult[0].user_id;
                    await insertAcceptedBusinessUser.run({
                        userId,
                        auth0UserId: effectiveAuth0UserId,
                        ownerId: invitation.business_id,
                        roleId: invitation.role_id,
                    }, client);
                }
                else {
                    userId = invitation.user_id;
                    await assignAuth0UserToInvitedUser();
                }
            }
            else {
                userId = invitation.user_id;
                await assignAuth0UserToInvitedUser();
            }
            if (invitation.auth0_user_id) {
                try {
                    if (auth0UserId && auth0UserId !== invitation.auth0_user_id) {
                        await this.auth0ManagementProvider.deleteUser(invitation.auth0_user_id);
                    }
                    else {
                        await this.auth0ManagementProvider.unblockUser(invitation.auth0_user_id);
                    }
                }
                catch (error) {
                    throw mapAuth0Error(error);
                }
            }
            await updateInvitationAcceptance.run({ id: invitation.id }, client);
            await this.auditLogsProvider.log({
                ownerId: invitation.business_id,
                userId,
                auth0UserId: effectiveAuth0UserId,
                action: 'INVITATION_ACCEPTED',
                entity: 'Invitation',
                entityId: invitation.id,
                details: {
                    auth0_user_id: effectiveAuth0UserId,
                    business_id: invitation.business_id,
                    role_id: invitation.role_id,
                },
            }, client);
            await client.query('COMMIT');
            return {
                success: true,
                businessId: invitation.business_id,
                roleId: invitation.role_id,
            };
        }
        catch (error) {
            await client.query('ROLLBACK').catch(() => null);
            throw error;
        }
        finally {
            client.release();
        }
    }
};
AcceptInvitationsProvider = __decorate([
    Injectable({
        scope: Scope.Operation,
        global: true,
    }),
    __metadata("design:paramtypes", [DBProvider,
        Auth0ManagementProvider,
        AuditLogsProvider])
], AcceptInvitationsProvider);
export { AcceptInvitationsProvider };
//# sourceMappingURL=accept-invitations.provider.js.map