const MongoClient = require(process.env.CSSVC_BACKEND_ROOT + '/shared/server_utils/mongo/mongo_client');
const ApiConfig = require(process.env.CSSVC_BACKEND_ROOT + '/api_server/config/config');
const ObjectId = require('mongodb').ObjectId;
const UserIndexes = require(process.env.CSSVC_BACKEND_ROOT + '/api_server/modules/users/indexes');
const RepoIndexes = require(process.env.CSSVC_BACKEND_ROOT + '/api_server/modules/repos/indexes');
const StreamIndexes = require(process.env.CSSVC_BACKEND_ROOT + '/api_server/modules/streams/indexes');
const EntityIndexes = require(process.env.CSSVC_BACKEND_ROOT + '/api_server/modules/entities/indexes');

const COLLECTIONS = ['companies', 'teams', 'repos', 'users', 'streams', 'posts', 'codemarks', 'reviews', 'codeErrors', 'markers', 'markerLocations', 'entities'];
const COLLECTIONS_FOR_TEAM = ['streams', 'posts', 'codemarks', 'reviews', 'codeErrors', 'markers', 'markerLocations', 'entities'];

class Deleter {

	async go (options) {
		Object.assign(this, options);
		this.logger = this.logger || console;
		await ApiConfig.loadPreferredConfig();
		await this.openMongoClient();
		await this.getTargetObject();
		await this.getUsers();
		await this.deleteRepos();
		await this.deleteTeam();
		await this.deleteCompany();
		await this.deleteUsers();
		if (this.teamId) {
			await Promise.all(
				COLLECTIONS_FOR_TEAM.map(async collection => {
					await this.deleteCollection(collection);
				})
			);
		}
	}

	async openMongoClient () {
		if (this.mongoClient) { return; }
		this.mongoClient = new MongoClient({ collections: COLLECTIONS });
		try {
			await this.mongoClient.openMongoClient(ApiConfig.getPreferredConfig().storage.mongo);
		}
		catch (error) {
			throw `unable to open mongo client: ${JSON.stringify(error)}`;
		}
	}

	async getTargetObject () {
		if (this.teamIdOrName) {
			await this.getTeam();
		}
		else if (this.repoId) {
			await this.getRepo();
		}
		else if (this.userIdOrEmail) {
			await this.getUser();
		}
		else {
			throw 'must provide team, repo, or user';
		}
	}

	async getTeam () {
		if (!this.teamIdOrName) {
			return;
		}
		this.teamId = ObjectId(this.teamIdOrName).toString();
		await this.getTeamById();
	}

	async getTeamById () {
		try {
			this.team = await this.mongoClient.mongoCollections.teams.getById(
				this.teamId,
				{ fields: ['id', 'companyId', 'name'] }
			);
		}
		catch (error) {
			throw `unable to fetch team: ${JSON.stringify(error)}`;
		}
	}

	async getTeamByName () {
		throw 'getting team by name is no longer supported';
		/*
		let teams;
		try {
			teams = await this.mongoClient.mongoCollections.teams.getByQuery(
				{
					name: this.teamIdOrName
				},
				{
					fields: ['id', 'companyId'],
					overrideHintRequired: true
				}
			);
		}
		catch (error) {
			throw `unable to fetch team: ${JSON.stringify(error)}`;
		}
		if (teams.length === 0) {
			throw `team not found: ${this.teamIdOrName}`;
		}
		this.team = teams[0];
		this.teamId = this.team.id;
		*/
	}

	async getRepo () {
		try {
			this.repo = this.mongoClient.mongoCollections.repos.getById(this.repoId);
		}
		catch (error) {
			throw `unable to fetch repo: ${JSON.stringify(error)}`;
		}
		if (!this.repo) {
			throw `repo not found: ${this.repoId}`;
		}
	}

	async getUser () {
		try {
			this.userId = ObjectId(this.userIdOrEmail).toString();
			await this.getUserById();
		}
		catch (error) {
			await this.getUserByEmail();
		}
	}

	async getUserByEmail () {
		let users;
		try {
			users = await this.mongoClient.mongoCollections.users.getByQuery(
				{
					searchableEmail: this.userIdOrEmail.toLowerCase()
				},
				{
					fields: ['id'],
					hint: UserIndexes.bySearchableEmail
				}
			);
		}
		catch (error) {
			throw `unable to fetch user by email: ${JSON.stringify(error)}`;
		}
		if (users.length === 0) {
			throw `user not found: ${this.userIdOrEmail}`;
		}
		this.user = users[0];
	}

	async getUserById () {
		try {
			this.user = await this.mongoClient.mongoCollections.users.getById(this.userIdOrEmail);
		}
		catch (error) {
			throw `unable to fetch user: ${JSON.stringify(error)}`;
		}
		if (!this.user) {
			throw `user not found: ${this.userIdOrEmail}`;
		}
	}

	async getUsers () {
		if (!this.teamId) {
			return;
		}
		try {
			this.users = await this.mongoClient.mongoCollections.users.getByQuery(
				{
					teamIds: this.teamId
				},
				{
					fields: ['id', 'teamIds', 'email'],
					hint: UserIndexes.byTeamIds
				}
			);
		}
		catch (error) {
			throw `unable to fetch users by team: ${JSON.stringify(error)}`;
		}
	}

	async getRepos () {
		if (!this.teamId) {
			return;
		}
		let repos;
		try {
			repos = await this.mongoClient.mongoCollections.repos.getByQuery(
				{
					teamId: this.teamId
				},
				{
					fields: ['id'],
					hint: RepoIndexes.byTeamId
				}
			);
		}
		catch (error) {
			throw `unable to fetch repos by team: ${JSON.stringify(error)}`;
		}
		this.repoIds = repos.map(repo => repo.id);
	}

	async getStreams () {
		let query, hint;
		if (this.teamId) {
			query = { teamId: this.teamId };
			hint = StreamIndexes.byTeamId; // ???
		}
		else {
			if (!this.repoIds && !this.repoId) {
				return;
			}
			this.repoIds = this.repoIds || [this.repoId];
			query = { repoId: { $in: this.repoIds } };
			hint = StreamIndexes.byFile;
		}
		let streams;
		try {
			streams = await this.mongoClient.mongoCollections.streams.getByQuery(
				query,
				{
					fields: ['id'],
					hint: hint
				}
			);
		}
		catch (error) {
			throw `unable to fetch streams by repo: ${JSON.stringify(error)}`;
		}
		this.streamIds = streams.map(stream => stream.id);
	}

	async deleteRepos () {
		if (this.teamId) {
			await this.deleteCollection('repos');
		}
		else if (this.repoId) {
			await this.deleteRepoById();
		}
	}

	async deleteRepoById () {
		const repoId = this.repoId.toLowerCase();
		this.logger.log(`Deactivating repo ${repoId}...`);
		try {
			await this.mongoClient.mongoCollections.repos.updateDirect(
				{ id: this.mongoClient.mongoCollections.repos.objectIdSafe(repoId) },
				{ $set: { deactivated: true } }
			);
		}
		catch (error) {
			throw `unable to delete repo: ${JSON.stringify(error)}`;
		}
	}

	async deleteTeam () {
		if (!this.teamId) {
			return ;
		}
		const teamId = this.teamId.toLowerCase();
		this.logger.log(`Deactivating team ${teamId}...`);
		try {
			const now = Date.now();
			const newName = `${this.team.name}-deactivated${now}`;
			await this.mongoClient.mongoCollections.teams.updateDirect(
				{ id: this.mongoClient.mongoCollections.teams.objectIdSafe(teamId) },
				{ $set: { deactivated: true, name: newName } }
			);
		}
		catch (error) {
			throw `unable to delete team: ${JSON.stringify(error)}`;
		}
	}

	async deleteCompany () {
		if (!this.team) {
			return;
		}
		this.company = await this.mongoClient.mongoCollections.companies.getById(this.team.companyId);
		if (!this.company) { return; }

		if ((this.company.teamIds || []).length === 1 && this.company.teamIds[0] === this.team.id) {
			this.logger.log(`Deactivating company ${this.team.companyId}...`);
			try {
				await this.mongoClient.mongoCollections.companies.updateDirect(
					{ id: this.mongoClient.mongoCollections.companies.objectIdSafe(this.team.companyId) },
					{ $set: { deactivated: true } }
				);
			}
			catch (error) {
				throw `unable to delete company: ${JSON.stringify(error)}`;
			}
		}
		else {
			try {
				this.logger.log(`Removing team from company ${this.team.companyId}...`);
				await this.mongoClient.mongoCollections.companies.updateDirect(
					{ id: this.mongoClient.mongoCollections.companies.objectIdSafe(this.team.companyId) },
					{ $pull: { teamIds: this.team.id } }
				);
			}
			catch (error) {
				throw `unable to remove team from company: ${JSON.stringify(error)}`;
			}
		}
	}

	async deleteUsers () {
		if (this.teamId) {
			await this.removeUsersFromTeam();
			if (this.deactivateTeamlessUsers) {
				await this.deleteWouldBeTeamlessUsers();
			}
		}
		else if (this.userIdOrEmail) {
			await this.deleteSingleUser();
		}
	}

	async deleteWouldBeTeamlessUsers () {
		const usersToDelete = this.users
			.filter(user => {
				return (
					(user.teamIds || []).length === 1 &&
					user.teamIds[0] === this.teamId
				);
			});

		if (usersToDelete.length === 0) {
			return;
		}
		this.logger.log(`Deactivating ${usersToDelete.length} users...`);
		try {
			await Promise.all(usersToDelete.map(async user => {
				if (this.emailsOutputStream) {
					await this.emailsOutputStream.write(`${user.email}\n`);
				}
				if (user.email) {
					const emailParts = user.email.split('@');
					const now = Date.now();
					const newEmail = `${emailParts[0]}-deactivated${now}@${emailParts[1]}`;
					await this.mongoClient.mongoCollections.users.updateDirect(
						{ id: this.mongoClient.mongoCollections.users.objectIdSafe(user.id) },
						{ $set: { deactivated: true, email: newEmail, searchableEmail: newEmail.toLowerCase() } }
					);
				}
			}));
		}
		catch (error) {
			const message = error instanceof Error ? error.message : JSON.stringify(error);
			console.trace();
			throw `unable to delete users: ${message}`;
		}
	}

	async removeUsersFromTeam () {
		this.logger.log('Removing users from team...');
		try {
			await this.mongoClient.mongoCollections.users.updateDirect(
				{ teamIds: this.teamId },
				{ $pull: { teamIds: this.teamId, companyIds: this.team.companyId } },
			);
		}
		catch (error) {
			throw `unable to remove users from team: ${JSON.stringify(error)}`;
		}
	}

	async deleteSingleUser () {
		this.logger.log(`Deactivating user ${this.userIdOrEmail}...`);
		try {
			const now = Date.now();
			const emailParts = this.user.email.split('@');
			const newEmail = `${emailParts[0]}-deactivated${now}@${emailParts[1]}`;
			await this.mongoClient.mongoCollections.users.updateDirect(
				{ id: this.mongoClient.mongoCollections.users.objectIdSafe(this.user.id) },
				{ $set: { deactivated: true, email: newEmail, searchableEmail: newEmail.toLowerCase() } }
			);
		}
		catch (error) {
			throw `unable to delete user: ${JSON.stringify(error)}`;
		}
	}

	async deleteCollection (collection) {
		this.logger.log(`Deactivating ${collection}...`);
		try {
			await this.mongoClient.mongoCollections[collection].updateDirect(
				{ teamId: this.teamId },
				{ $set: { deactivated: true } }
			);
		}
		catch (error) {
			const message = error instanceof Error ? error.message : JSON.stringify(error);
			throw `unable to delete ${collection}: ${message}`;
		}
	}
}

module.exports = Deleter;
