import * as fs from 'fs';
import * as path from 'path';
// eslint-disable-next-line
// @ts-ignore
import debugPkg from 'debug';
import { decodeJwt } from 'jose';
import * as yaml from 'js-yaml';
import { fetch } from '@whatwg-node/fetch';
import { Cluster } from './Cluster.js';
import { clusterEndpointMap } from './constants.js';
import { ClusterNotFound } from './errors/ClusterNotFound.js';
import { ClusterNotSet } from './errors/ClusterNotSet.js';
import { Output } from './Output.js';
import { Variables } from './Variables.js';
const debug = debugPkg('Environment');
export class Environment {
    constructor(home, out = new Output(), version) {
        this.sharedClusters = ['prisma-eu1', 'prisma-us1'];
        this.clusterEndpointMap = clusterEndpointMap;
        this.globalRC = {};
        this.clustersFetched = false;
        this.out = out;
        this.home = home;
        this.version = version;
        this.rcPath = path.join(this.home, '.prisma/config.yml');
        fs.mkdirSync(path.dirname(this.rcPath), { recursive: true });
    }
    _getClusters() {
        const clusters = this.clusters;
        if (clusters === undefined) {
            throw new Error(`Cannot get clusters. Did you forget to call "Environment.load()"?`);
        }
        return clusters;
    }
    async load() {
        await this.loadGlobalRC();
    }
    get cloudSessionKey() {
        return process.env['PRISMA_CLOUD_SESSION_KEY'] || this.globalRC.cloudSessionKey;
    }
    async renewToken() {
        if (this.cloudSessionKey) {
            const data = decodeJwt(this.cloudSessionKey);
            if (!data.exp) {
                return;
            }
            const timeLeft = data.exp * 1000 - Date.now();
            if (timeLeft < 1000 * 60 * 60 * 24 && timeLeft > 0) {
                try {
                    const res = await this.requestCloudApi(`
          mutation {
            renewToken
          }
        `);
                    if (res.renewToken) {
                        this.globalRC.cloudSessionKey = res.renewToken;
                        this.saveGlobalRC();
                    }
                }
                catch (e) {
                    debug(e);
                }
            }
        }
    }
    async fetchClusters() {
        if (!this.clustersFetched && this.cloudSessionKey) {
            const renewPromise = this.renewToken();
            try {
                const res = (await Promise.race([
                    this.requestCloudApi(`
            query prismaCliGetClusters {
              me {
                memberships {
                  workspace {
                    id
                    slug
                    clusters {
                      id
                      name
                      connectInfo {
                        endpoint
                      }
                      customConnectionInfo {
                        endpoint
                      }
                    }
                  }
                }
              }
            }
          `),
                    // eslint-disable-next-line
                    new Promise((_, r) => setTimeout(() => r(), 6000)),
                ]));
                if (!res) {
                    return;
                }
                if (res.me && res.me.memberships && Array.isArray(res.me.memberships)) {
                    // clean up all prisma-eu1 and prisma-us1 clusters if they already exist
                    this.clusters = this._getClusters().filter(c => c.name !== 'prisma-eu1' && c.name !== 'prisma-us1');
                    for (const m of res.me.memberships) {
                        for (const cluster of m.workspace.clusters) {
                            const endpoint = cluster.connectInfo
                                ? cluster.connectInfo.endpoint
                                : cluster.customConnectionInfo
                                    ? cluster.customConnectionInfo.endpoint
                                    : this.clusterEndpointMap[cluster.name];
                            this.addCluster(new Cluster(this.out, cluster.name, endpoint, this.globalRC.cloudSessionKey, false, ['prisma-eu1', 'prisma-us1'].includes(cluster.name), !['prisma-eu1', 'prisma-us1'].includes(cluster.name), m.workspace.slug));
                        }
                    }
                }
            }
            catch (e) {
                debug(e);
            }
            await renewPromise;
        }
    }
    clusterByName(name, throws = false) {
        if (!this.clusters) {
            return;
        }
        const cluster = this.clusters.find(c => c.name === name);
        if (!throws) {
            return cluster;
        }
        if (!cluster) {
            if (!name) {
                throw new ClusterNotSet();
            }
            throw new ClusterNotFound(name);
        }
        return cluster;
    }
    setToken(token) {
        this.globalRC.cloudSessionKey = token;
    }
    addCluster(cluster) {
        const clusters = this._getClusters();
        const existingClusterIndex = clusters.findIndex(c => {
            if (cluster.workspaceSlug) {
                return c.workspaceSlug === cluster.workspaceSlug && c.name === cluster.name;
            }
            else {
                return c.name === cluster.name;
            }
        });
        if (existingClusterIndex > -1) {
            clusters.splice(existingClusterIndex, 1);
        }
        clusters.push(cluster);
    }
    removeCluster(name) {
        this.clusters = this._getClusters().filter(c => c.name !== name);
    }
    saveGlobalRC() {
        const rc = {
            cloudSessionKey: this.globalRC.cloudSessionKey
                ? this.globalRC.cloudSessionKey.trim()
                : undefined,
            clusters: this.getLocalClusterConfig(),
        };
        // parse & stringify to rm undefined for yaml parser
        const rcString = yaml.dump(JSON.parse(JSON.stringify(rc)));
        fs.writeFileSync(this.rcPath, rcString);
    }
    setActiveCluster(cluster) {
        this.activeCluster = cluster;
    }
    async loadGlobalRC() {
        if (this.rcPath) {
            try {
                fs.accessSync(this.rcPath);
                const globalFile = fs.readFileSync(this.rcPath, 'utf-8');
                await this.parseGlobalRC(globalFile);
            }
            catch {
                await this.parseGlobalRC();
            }
        }
        else {
            await this.parseGlobalRC();
        }
    }
    async parseGlobalRC(globalFile) {
        if (globalFile) {
            this.globalRC = await this.loadYaml(globalFile, this.rcPath);
        }
        this.clusters = this.initClusters(this.globalRC);
    }
    async loadYaml(file, filePath = null) {
        if (file) {
            let content;
            try {
                content = yaml.load(file);
            }
            catch (e) {
                throw new Error(`Yaml parsing error in ${filePath}: ${e.message}`);
            }
            const variables = new Variables(filePath || 'no filepath provided', this.args, this.out);
            content = await variables.populateJson(content);
            return content;
        }
        else {
            return {};
        }
    }
    initClusters(rc) {
        const sharedClusters = this.getSharedClusters(rc);
        return [...sharedClusters];
    }
    getSharedClusters(rc) {
        return this.sharedClusters.map(clusterName => {
            return new Cluster(this.out, clusterName, this.clusterEndpointMap[clusterName], rc && rc.cloudSessionKey, false, true);
        });
    }
    getLocalClusterConfig() {
        return this._getClusters()
            .filter(c => !c.shared && c.clusterSecret !== this.cloudSessionKey && !c.isPrivate)
            .reduce((acc, cluster) => {
            return {
                ...acc,
                [cluster.name]: {
                    host: cluster.baseUrl,
                    clusterSecret: cluster.clusterSecret,
                },
            };
        }, {});
    }
    async requestCloudApi(query) {
        const res = await fetch('https://api.cloud.prisma.sh', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                Authorization: `Bearer ${this.cloudSessionKey}`,
                'X-Cli-Version': this.version,
            },
            body: JSON.stringify({
                query,
            }),
        });
        const json = await res.json();
        return json.data;
    }
}
export const isLocal = (hostname) => hostname.includes('localhost') || hostname.includes('127.0.0.1');
