import { deleteSyncConfig, deleteSyncFilesForConfig, getSyncConfig, getSyncConfigByParams } from './config/config.service.js';
import connectionService from '../connection.service.js';
import { getLatestSyncJob } from './job.service.js';
import { createSync, getSyncsByConnectionId, getSyncsByProviderConfigKey, getSyncsByProviderConfigAndSyncNames, getSyncByIdAndName, getSyncNamesByConnectionId, softDeleteSync } from './sync.service.js';
import { errorNotificationService } from '../notification/error.service.js';
import configService from '../config.service.js';
import { SyncType, SyncStatus } from '../../models/Sync.js';
import { NangoError } from '../../utils/error.js';
import { getLogger, stringifyError } from '@nangohq/utils';
import environmentService from '../environment.service.js';
// Should be in "logs" package but impossible thanks to CLI
export const syncCommandToOperation = {
    PAUSE: 'pause',
    UNPAUSE: 'unpause',
    RUN: 'request_run',
    RUN_FULL: 'request_run_full',
    CANCEL: 'cancel'
};
const logger = getLogger('sync.manager');
export class SyncManagerService {
    async createSyncForConnection(nangoConnectionId, logContextGetter, orchestrator) {
        const nangoConnection = (await connectionService.getConnectionById(nangoConnectionId));
        const nangoConfig = await getSyncConfig({ nangoConnection });
        if (!nangoConfig) {
            logger.error('Failed to load the Nango config - will not start any syncs! If you expect to see a sync make sure you used the nango cli deploy command');
            return;
        }
        const { integrations } = nangoConfig;
        const providerConfigKey = nangoConnection?.provider_config_key;
        if (!integrations[providerConfigKey]) {
            return;
        }
        const providerConfig = (await configService.getProviderConfig(nangoConnection?.provider_config_key, nangoConnection?.environment_id));
        const syncObject = integrations[providerConfigKey];
        const syncNames = Object.keys(syncObject);
        for (const syncName of syncNames) {
            const syncData = syncObject[syncName];
            if (!syncData.enabled) {
                continue;
            }
            const syncConfig = await getSyncConfigByParams(nangoConnection.environment_id, syncName, providerConfigKey);
            if (!syncConfig) {
                continue;
            }
            const sync = await createSync(nangoConnectionId, syncConfig);
            if (sync) {
                await orchestrator.scheduleSync({
                    nangoConnection,
                    sync,
                    providerConfig,
                    syncName,
                    syncData,
                    logContextGetter
                });
            }
        }
    }
    async createSyncForConnections(connections, syncName, providerConfigKey, environmentId, sync, logContextGetter, orchestrator, debug = false, logCtx) {
        try {
            const providerConfig = await configService.getProviderConfig(providerConfigKey, environmentId);
            if (!providerConfig) {
                throw new Error(`Provider config not found for ${providerConfigKey} in environment ${environmentId}`);
            }
            if (debug) {
                await logCtx?.debug(`Beginning iteration of starting syncs for ${syncName} with ${connections.length} connections`);
            }
            for (const connection of connections) {
                const syncConfig = await getSyncConfigByParams(connection.environment_id, syncName, providerConfigKey);
                if (!syncConfig) {
                    continue;
                }
                const createdSync = await createSync(connection.id, syncConfig);
                if (!createdSync) {
                    continue;
                }
                await orchestrator.scheduleSync({
                    nangoConnection: connection,
                    sync: createdSync,
                    providerConfig: providerConfig,
                    syncName,
                    syncData: { ...sync, returns: sync.models, input: '' },
                    logContextGetter,
                    debug
                });
            }
            if (debug) {
                await logCtx?.debug(`Finished iteration of starting syncs for ${syncName} with ${connections.length} connections`);
            }
            return true;
        }
        catch (e) {
            await logCtx?.error(`Error starting syncs for ${syncName} with ${connections.length} connections`, { error: e });
            return false;
        }
    }
    async createSyncs(syncArgs, logContextGetter, orchestrator, debug = false, logCtx) {
        let success = true;
        for (const syncToCreate of syncArgs) {
            const { connections, providerConfigKey, environmentId, sync, syncName } = syncToCreate;
            const result = await this.createSyncForConnections(connections, syncName, providerConfigKey, environmentId, sync, logContextGetter, orchestrator, debug, logCtx);
            if (!result) {
                success = false;
            }
        }
        return success;
    }
    /**
     * Delete
     * @desc delete a sync and all the related objects
     * 1) sync config files
     * 2) sync config
     */
    async deleteConfig(syncConfigId, environmentId) {
        await deleteSyncFilesForConfig(syncConfigId, environmentId);
        await deleteSyncConfig(syncConfigId);
    }
    async softDeleteSync(syncId, environmentId, orchestrator) {
        await orchestrator.deleteSync({ syncId, environmentId });
        await softDeleteSync(syncId);
        await errorNotificationService.sync.clearBySyncId({ sync_id: syncId });
    }
    async softDeleteSyncsByConnection(connection, orchestrator) {
        const syncs = await getSyncsByConnectionId(connection.id);
        if (!syncs) {
            return;
        }
        for (const sync of syncs) {
            await this.softDeleteSync(sync.id, connection.environment_id, orchestrator);
        }
    }
    async deleteSyncsByProviderConfig(environmentId, providerConfigKey, orchestrator) {
        const syncs = await getSyncsByProviderConfigKey(environmentId, providerConfigKey);
        if (!syncs) {
            return;
        }
        for (const sync of syncs) {
            await this.softDeleteSync(sync.id, environmentId, orchestrator);
        }
    }
    async runSyncCommand({ recordsService, orchestrator, environment, providerConfigKey, syncNames, command, logContextGetter, connectionId, initiator }) {
        const provider = await configService.getProviderConfig(providerConfigKey, environment.id);
        const account = (await environmentService.getAccountFromEnvironment(environment.id));
        const logCtx = await logContextGetter.create({ operation: { type: 'sync', action: syncCommandToOperation[command] } }, { account, environment, integration: { id: provider.id, name: provider.unique_key, provider: provider.provider } });
        if (connectionId) {
            const { success, error, response: connection } = await connectionService.getConnection(connectionId, providerConfigKey, environment.id);
            if (!success || !connection) {
                return { success: false, error, response: false };
            }
            let syncs = syncNames;
            if (syncs.length === 0) {
                syncs = await getSyncNamesByConnectionId(connection.id);
            }
            for (const syncName of syncs) {
                const sync = await getSyncByIdAndName(connection.id, syncName);
                if (!sync) {
                    throw new Error(`Sync "${syncName}" doesn't exists.`);
                }
                await orchestrator.runSyncCommand({
                    connectionId: connection.id,
                    syncId: sync.id,
                    command,
                    environmentId: environment.id,
                    logCtx,
                    recordsService,
                    initiator
                });
            }
        }
        else {
            const syncs = syncNames.length > 0
                ? await getSyncsByProviderConfigAndSyncNames(environment.id, providerConfigKey, syncNames)
                : await getSyncsByProviderConfigKey(environment.id, providerConfigKey);
            if (!syncs) {
                const error = new NangoError('no_syncs_found');
                return { success: false, error, response: false };
            }
            for (const sync of syncs) {
                const connection = await connectionService.getConnectionById(sync.nango_connection_id);
                if (!connection) {
                    continue;
                }
                await orchestrator.runSyncCommand({
                    connectionId: connection.id,
                    syncId: sync.id,
                    command,
                    environmentId: environment.id,
                    logCtx,
                    recordsService,
                    initiator
                });
            }
        }
        await logCtx.info('Sync was successfully updated', { command, syncNames });
        await logCtx.success();
        return { success: true, error: null, response: true };
    }
    async getSyncStatus(environmentId, providerConfigKey, syncNames, orchestrator, recordsService, connectionId, includeJobStatus = false, optionalConnection) {
        const syncsWithStatus = [];
        let connection = optionalConnection;
        if (connectionId && !connection) {
            const connectionResult = await connectionService.getConnection(connectionId, providerConfigKey, environmentId);
            if (!connectionResult.success || !connectionResult.response) {
                return { success: false, error: connectionResult.error, response: null };
            }
            connection = connectionResult.response;
        }
        if (connection) {
            for (const syncName of syncNames) {
                const sync = await getSyncByIdAndName(connection?.id, syncName);
                if (!sync) {
                    continue;
                }
                const reportedStatus = await this.syncStatus({ sync, environmentId, providerConfigKey, includeJobStatus, orchestrator, recordsService });
                syncsWithStatus.push(reportedStatus);
            }
        }
        else {
            const syncs = syncNames.length > 0
                ? await getSyncsByProviderConfigAndSyncNames(environmentId, providerConfigKey, syncNames)
                : await getSyncsByProviderConfigKey(environmentId, providerConfigKey);
            if (!syncs) {
                return { success: true, error: null, response: syncsWithStatus };
            }
            for (const sync of syncs) {
                const reportedStatus = await this.syncStatus({ sync, environmentId, providerConfigKey, includeJobStatus, orchestrator, recordsService });
                syncsWithStatus.push(reportedStatus);
            }
        }
        return { success: true, error: null, response: syncsWithStatus };
    }
    classifySyncStatus(jobStatus, scheduleState) {
        if (jobStatus === SyncStatus.RUNNING) {
            return SyncStatus.RUNNING;
        }
        switch (scheduleState) {
            case 'PAUSED':
                return SyncStatus.PAUSED;
            case 'STARTED':
                if (jobStatus === SyncStatus.STOPPED) {
                    // job status doesn't have a ERROR status
                    return SyncStatus.ERROR;
                }
                return jobStatus || SyncStatus.SUCCESS;
            default:
                return SyncStatus.STOPPED;
        }
    }
    /**
     * Trigger If Connections Exist
     * @desc for the recently deploy flows, create the sync and trigger it if there are connections
     */
    async triggerIfConnectionsExist(flows, environmentId, logContextGetter, orchestrator) {
        for (const flow of flows) {
            if (flow.type === 'action') {
                continue;
            }
            const existingConnections = await connectionService.getConnectionsByEnvironmentAndConfig(environmentId, flow.providerConfigKey);
            if (existingConnections.length === 0) {
                continue;
            }
            const { providerConfigKey } = flow;
            const name = flow.name || flow.syncName;
            await this.createSyncForConnections(existingConnections, name, providerConfigKey, environmentId, flow, logContextGetter, orchestrator, false);
        }
    }
    async syncStatus({ sync, environmentId, providerConfigKey, includeJobStatus, orchestrator, recordsService }) {
        const latestJob = await getLatestSyncJob(sync.id);
        const schedules = await orchestrator.searchSchedules([{ syncId: sync.id, environmentId }]);
        if (schedules.isErr()) {
            throw new Error(`Failed to get schedule for sync ${sync.id} in environment ${environmentId}: ${stringifyError(schedules.error)}`);
        }
        const schedule = schedules.value.get(sync.id);
        let frequency = sync.frequency;
        const syncConfig = await getSyncConfigByParams(environmentId, sync.name, providerConfigKey);
        if (!frequency) {
            frequency = syncConfig?.runs || null;
        }
        if (!schedule) {
            throw new Error(`Schedule for sync ${sync.id} and environment ${environmentId} not found`);
        }
        const countRes = await recordsService.getRecordCountsByModel({ connectionId: sync.nango_connection_id, environmentId });
        if (countRes.isErr()) {
            throw new Error(`Failed to get records count for sync ${sync.id} in environment ${environmentId}: ${stringifyError(countRes.error)}`);
        }
        const recordCount = syncConfig?.models.reduce((acc, model) => {
            acc[model] = countRes.isOk() ? countRes.value[model]?.count || 0 : 0;
            return acc;
        }, {}) || {};
        return {
            id: sync.id,
            type: latestJob?.type === SyncType.INCREMENTAL ? latestJob.type : 'INITIAL',
            finishedAt: latestJob?.updated_at,
            nextScheduledSyncAt: schedule.nextDueDate,
            name: sync.name,
            status: this.classifySyncStatus(latestJob?.status, schedule.state),
            frequency,
            latestResult: latestJob?.result,
            latestExecutionStatus: latestJob?.status,
            recordCount,
            ...(includeJobStatus ? { jobStatus: latestJob?.status } : {})
        };
    }
}
export default new SyncManagerService();
//# sourceMappingURL=manager.service.js.map