"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.concatName = exports.PrismaDefinitionClass = void 0;
const tslib_1 = require("tslib");
const buffer_1 = require("buffer");
const fs = tslib_1.__importStar(require("fs"));
const path = tslib_1.__importStar(require("path"));
const chalk_1 = tslib_1.__importDefault(require("chalk"));
const dotenv = tslib_1.__importStar(require("dotenv"));
const jose_1 = require("jose");
const Cluster_js_1 = require("./Cluster.js");
const parseEndpoint_js_1 = require("./utils/parseEndpoint.js");
const yamlComment_js_1 = require("./utils/yamlComment.js");
const yaml_js_1 = require("./yaml.js");
class PrismaDefinitionClass {
    constructor(env, definitionPath, envVars = process.env, out) {
        this.secrets = null;
        this.definitionPath = definitionPath;
        if (definitionPath) {
            this.definitionDir = path.dirname(definitionPath);
        }
        this.env = env;
        this.out = out;
        this.envVars = envVars;
    }
    async load(args, envPath, graceful) {
        if (args['project']) {
            const flagPath = path.resolve(String(args['project']));
            try {
                fs.accessSync(flagPath);
            }
            catch {
                throw new Error(`Prisma definition path specified by --project '${flagPath}' does not exist`);
            }
            this.definitionPath = flagPath;
            this.definitionDir = path.dirname(flagPath);
            await this.loadDefinition(args, graceful);
            this.validate();
            return;
        }
        if (envPath) {
            try {
                fs.accessSync(envPath);
            }
            catch {
                envPath = path.join(process.cwd(), envPath);
            }
            try {
                fs.accessSync(envPath);
            }
            catch {
                throw new Error(`--env-file path '${envPath}' does not exist`);
            }
        }
        dotenv.config({ path: envPath });
        if (this.definitionPath) {
            await this.loadDefinition(args, graceful);
            this.validate();
        }
        else {
            throw new Error(`Couldn’t find \`prisma.yml\` file. Are you in the right directory?`);
        }
    }
    async loadDefinition(args, graceful) {
        const { definition, rawJson } = await (0, yaml_js_1.readDefinition)(this.definitionPath, args, this.out, this.envVars, graceful);
        this.rawEndpoint = rawJson.endpoint;
        this.definition = definition;
        this.rawJson = rawJson;
        this.definitionString = fs.readFileSync(this.definitionPath, 'utf-8');
        this.typesString = this.getTypesString(this.definition);
        const secrets = this.definition.secret;
        this.secrets = secrets ? secrets.replace(/\s/g, '').split(',') : null;
    }
    get endpoint() {
        return ((this.definition && this.definition.endpoint) || process.env['PRISMA_MANAGEMENT_API_ENDPOINT']);
    }
    get clusterBaseUrl() {
        if (!this.definition || !this.endpoint) {
            return undefined;
        }
        const { clusterBaseUrl } = (0, parseEndpoint_js_1.parseEndpoint)(this.endpoint);
        return clusterBaseUrl;
    }
    get service() {
        if (!this.definition) {
            return undefined;
        }
        if (!this.endpoint) {
            return undefined;
        }
        const { service } = (0, parseEndpoint_js_1.parseEndpoint)(this.endpoint);
        return service;
    }
    get stage() {
        if (!this.definition) {
            return undefined;
        }
        if (!this.endpoint) {
            return undefined;
        }
        const { stage } = (0, parseEndpoint_js_1.parseEndpoint)(this.endpoint);
        return stage;
    }
    get cluster() {
        if (!this.definition) {
            return undefined;
        }
        if (!this.endpoint) {
            return undefined;
        }
        const { clusterName } = (0, parseEndpoint_js_1.parseEndpoint)(this.endpoint);
        return clusterName;
    }
    validate() {
        // shared clusters need a workspace
        const clusterName = this.getClusterName();
        const cluster = this.env.clusterByName(clusterName);
        if (this.definition &&
            clusterName &&
            cluster &&
            cluster.shared &&
            !cluster.isPrivate &&
            !this.getWorkspace() &&
            clusterName !== 'shared-public-demo') {
            throw new Error(`Your \`cluster\` property in the prisma.yml is missing the workspace slug.
Make sure that your \`cluster\` property looks like this: ${chalk_1.default.bold('<workspace>/<cluster-name>')}. You can also remove the cluster property from the prisma.yml
and execute ${chalk_1.default.bold.green('prisma deploy')} again, to get that value auto-filled.`);
        }
        if (this.definition &&
            this.definition.endpoint &&
            clusterName &&
            cluster &&
            cluster.shared &&
            !cluster.isPrivate &&
            !this.getWorkspace() &&
            clusterName !== 'shared-public-demo') {
            throw new Error(`The provided endpoint ${this.definition.endpoint} points to a demo cluster, but is missing the workspace slug. A valid demo endpoint looks like this: https://eu1.prisma.sh/myworkspace/service-name/stage-name`);
        }
        if (this.definition &&
            this.definition.endpoint &&
            !this.definition.endpoint.startsWith('http')) {
            throw new Error(`${chalk_1.default.bold(this.definition.endpoint)} is not a valid endpoint. It must start with http:// or https://`);
        }
    }
    async getToken(serviceName, stageName) {
        if (this.secrets) {
            const data = {
                data: {
                    service: `${serviceName}@${stageName}`,
                    roles: ['admin'],
                },
            };
            return new jose_1.SignJWT(data)
                .setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
                .setIssuedAt()
                .setExpirationTime('7d')
                .sign(buffer_1.Buffer.from(this.secrets[0]));
        }
        return undefined;
    }
    async getCluster(_ = false) {
        if (this.definition && this.endpoint) {
            const clusterData = (0, parseEndpoint_js_1.parseEndpoint)(this.endpoint);
            const cluster = await this.getClusterByEndpoint(clusterData);
            this.env.removeCluster(clusterData.clusterName);
            this.env.addCluster(cluster);
            return cluster;
        }
        return undefined;
    }
    findClusterByBaseUrl(baseUrl) {
        return this.env.clusters?.find(c => c.baseUrl.toLowerCase() === baseUrl);
    }
    async getClusterByEndpoint(data) {
        if (data.clusterBaseUrl && !process.env['PRISMA_MANAGEMENT_API_SECRET']) {
            const cluster = this.findClusterByBaseUrl(data.clusterBaseUrl);
            if (cluster) {
                return cluster;
            }
        }
        const { clusterName, clusterBaseUrl, isPrivate, local, shared, workspaceSlug } = data;
        // if the cluster could potentially be served by the cloud api, fetch the available
        // clusters from the cloud api
        if (!local) {
            await this.env.fetchClusters();
            const cluster = this.findClusterByBaseUrl(data.clusterBaseUrl);
            if (cluster) {
                return cluster;
            }
        }
        return new Cluster_js_1.Cluster(this.out, clusterName, clusterBaseUrl, shared || isPrivate ? this.env.cloudSessionKey : undefined, local, shared, isPrivate, workspaceSlug);
    }
    getTypesString(definition) {
        const typesPaths = definition.datamodel
            ? Array.isArray(definition.datamodel)
                ? definition.datamodel
                : [definition.datamodel]
            : [];
        let allTypes = '';
        for (const unresolvedTypesPath of typesPaths) {
            const typesPath = path.join(this.definitionDir, unresolvedTypesPath);
            try {
                fs.accessSync(typesPath);
                const types = fs.readFileSync(typesPath, 'utf-8');
                allTypes += types + '\n';
            }
            catch {
                throw new Error(`The types definition file "${typesPath}" could not be found.`);
            }
        }
        return allTypes;
    }
    getClusterName() {
        return this.cluster || null;
    }
    getWorkspace() {
        if (this.definition && this.endpoint) {
            const { workspaceSlug } = (0, parseEndpoint_js_1.parseEndpoint)(this.endpoint);
            if (workspaceSlug) {
                return workspaceSlug;
            }
        }
        return null;
    }
    async getDeployName() {
        const cluster = await this.getCluster();
        return concatName(cluster, this.service, this.getWorkspace());
    }
    getSubscriptions() {
        if (this.definition && this.definition.subscriptions) {
            return Object.entries(this.definition.subscriptions).map(([name, subscription]) => {
                const url = typeof subscription.webhook === 'string'
                    ? subscription.webhook
                    : subscription.webhook.url;
                const headers = typeof subscription.webhook === 'string'
                    ? []
                    : transformHeaders(subscription.webhook.headers);
                let query = subscription.query;
                if (subscription.query.endsWith('.graphql')) {
                    const queryPath = path.join(this.definitionDir, subscription.query);
                    try {
                        fs.accessSync(queryPath);
                    }
                    catch {
                        throw new Error(`Subscription query ${queryPath} provided in subscription "${name}" in prisma.yml does not exist.`);
                    }
                    query = fs.readFileSync(queryPath, 'utf-8');
                }
                return {
                    name,
                    query,
                    headers,
                    url,
                };
            });
        }
        return [];
    }
    replaceEndpoint(newEndpoint) {
        this.definitionString = (0, yamlComment_js_1.replaceYamlValue)(this.definitionString, 'endpoint', newEndpoint);
        fs.writeFileSync(this.definitionPath, this.definitionString);
    }
    addDatamodel(datamodel) {
        this.definitionString += `\ndatamodel: ${datamodel}`;
        fs.writeFileSync(this.definitionPath, this.definitionString);
        this.definition.datamodel = datamodel;
    }
    async getEndpoint(serviceInput, stageInput) {
        const cluster = await this.getCluster();
        const service = serviceInput || this.service;
        const stage = stageInput || this.stage;
        const workspace = this.getWorkspace();
        if (service && stage && cluster) {
            return cluster.getApiEndpoint(service, stage, workspace);
        }
        return null;
    }
    getHooks(hookType) {
        if (this.definition && this.definition.hooks && this.definition.hooks[hookType]) {
            const hooks = this.definition.hooks[hookType];
            if (typeof hooks !== 'string' && !Array.isArray(hooks)) {
                throw new Error(`Hook ${hookType} provided in prisma.yml must be string or an array of strings.`);
            }
            return typeof hooks === 'string' ? [hooks] : hooks;
        }
        return [];
    }
}
exports.PrismaDefinitionClass = PrismaDefinitionClass;
function concatName(cluster, name, workspace) {
    if (cluster.shared) {
        const workspaceString = workspace ? `${workspace}~` : '';
        return `${workspaceString}${name}`;
    }
    return name;
}
exports.concatName = concatName;
function transformHeaders(headers) {
    if (!headers) {
        return [];
    }
    return Object.entries(headers).map(([name, value]) => ({ name, value }));
}
