import { deepClone, pascalCase } from '@takeshape/util';
import isObject from 'lodash/isObject.js';
import isString from 'lodash/isString.js';
import isUndefined from 'lodash/isUndefined.js';
import pReduce from 'p-reduce';
import { isAnyServiceConfig } from "../../types/utils.js";
/**
 * `graphql:my-key` `my-key`
 */
function parseV3ServiceStr(service) {
    const parts = service.split(':');
    return {
        provider: parts.length > 1 ? parts[0] : undefined,
        id: parts.length > 1 ? parts[1] : parts[0]
    };
}
/**
 * Ensures a consistent service config, inluding updating the service
 * authentication object.
 */
function updateServiceConfigV3ToV3_1(serviceConfig, serviceKey) {
    if (isAnyServiceConfig(serviceConfig)) {
        if (isString(serviceConfig.authentication) || isUndefined(serviceConfig.authentication)) {
            return serviceConfig;
        }
        return serviceConfig;
    }
    const { auth } = serviceConfig;
    const serviceParams = serviceConfig.params || {};
    let { provider: serviceKeyProvider, id: serviceKeyId } = parseV3ServiceStr(serviceKey);
    const authType = serviceParams.authType || undefined;
    const title = serviceParams.name || serviceKeyId;
    if (serviceKeyId === 'vercel') {
        serviceKeyProvider = 'vercel';
    }
    else if (serviceKeyId === 'netlify') {
        serviceKeyProvider = 'netlify';
    }
    // Example schemas had this in params, which are untyped, so adding here just in case
    if (serviceParams.type === 'rest') {
        serviceKeyProvider = 'rest';
    }
    else if (serviceParams.type === 'graphql' && serviceKeyProvider !== 'shopify') {
        serviceKeyProvider = 'graphql';
    }
    let provider;
    let authenticationType;
    let serviceType;
    let namespace;
    if (serviceKeyProvider === 'shopify') {
        provider = 'shopify';
        authenticationType = 'oauth2Bearer';
        serviceType = 'graphql';
        namespace = serviceParams.namespace ?? 'Shopify';
    }
    else if (serviceKeyProvider === 'takeshape') {
        provider = 'takeshape';
        authenticationType = 'none';
        serviceType = 'takeshape';
        namespace = serviceParams.namespace ?? 'TakeShape';
    }
    else if (serviceKeyProvider === 'bigcommerce') {
        provider = 'bigcommerce';
        authenticationType = 'bearer';
        serviceType = 'graphql';
        namespace = serviceParams.namespace ?? 'BigCommerce';
    }
    else if (serviceKeyProvider === 'rest') {
        provider = 'generic';
        authenticationType = mapAuthType(authType, auth);
        serviceType = 'rest';
        namespace = serviceParams.namespace ?? pascalCase(title);
    }
    else if (serviceKeyProvider === 'graphql') {
        provider = 'generic';
        authenticationType = mapAuthType(authType, auth);
        serviceType = 'graphql';
        namespace = serviceParams.namespace ?? pascalCase(title);
    }
    else if (serviceKeyProvider === 'vercel') {
        provider = 'vercel';
        authenticationType = 'oauth2Bearer';
        serviceType = 'deployment';
    }
    else if (serviceKeyProvider === 'netlify') {
        provider = 'netlify';
        authenticationType = 'oauth2Bearer';
        serviceType = 'deployment';
    }
    else {
        provider = 'generic';
        authenticationType = mapAuthType(authType, auth);
        serviceType = 'unknown';
        namespace = serviceParams.namespace ?? pascalCase(title);
    }
    const updatedServiceConfig = {
        title,
        id: serviceKey,
        provider,
        namespace,
        serviceType,
        authenticationType,
        options: {
            ...serviceParams
        }
    };
    if (serviceConfig.auth && isObject(serviceConfig.auth)) {
        const authentication = updateServiceAuthentication(updatedServiceConfig.authenticationType, serviceConfig.auth, serviceParams);
        return {
            ...updatedServiceConfig,
            authentication
        };
    }
    return updatedServiceConfig;
}
/**
 * V1/V3 unencrypted `auth` -> V3.1 `authentication
 */
function updateServiceAuthentication(authenticationType, legacyAuth, legacyParams) {
    if (authenticationType === 'oauth2Bearer') {
        return {
            token: legacyAuth.accessToken,
            scope: legacyAuth.scope,
            header: legacyParams.authHeader
        };
    }
    if (authenticationType === 'searchParams' && legacyParams.authProp) {
        return [
            {
                name: legacyParams.authProp,
                value: legacyAuth.accessToken
            }
        ];
    }
    if (authenticationType === 'basic') {
        const [username, password] = Buffer.from(legacyAuth.accessToken, 'base64').toString('utf-8').split(':');
        return {
            username,
            password
        };
    }
    if (authenticationType === 'bearer') {
        return {
            token: legacyAuth.accessToken,
            header: legacyParams.authHeader
        };
    }
}
function mapAuthType(authType, auth) {
    if (authType === 'none') {
        return 'none';
    }
    if (authType === 'searchParams') {
        return 'searchParams';
    }
    if (authType === 'bearer') {
        return 'bearer';
    }
    if (authType === 'bearer') {
        return 'basic';
    }
    if (!auth) {
        return 'none';
    }
    if (auth) {
        return 'bearer';
    }
    return 'unknown';
}
const migrate = async (context, projectSchema) => {
    const { encryptFn, decryptFn } = context;
    let services;
    /**
     * Services can only be migrated if a dataKey is present, otherwise they will need to be discarded.
     */
    if (projectSchema.services) {
        services = await pReduce(Object.entries(projectSchema.services), async (serviceMap, [serviceKey, serviceConfig]) => {
            const auth = serviceConfig.auth && decryptFn(serviceConfig.auth);
            const updatedServiceConfig = updateServiceConfigV3ToV3_1({ ...serviceConfig, auth }, serviceKey);
            const authentication = updatedServiceConfig?.authentication;
            serviceMap[serviceKey] = {
                ...updatedServiceConfig,
                authentication: authentication && isObject(authentication) ? encryptFn(authentication) : authentication
            };
            return serviceMap;
        }, {});
    }
    return deepClone({
        ...projectSchema,
        services,
        schemaVersion: '3.1.0'
    });
};
export default migrate;
