import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import {
	Workspace,
	Secret,
	Membership,
	MembershipOrg,
	Integration,
	IntegrationAuth,
	Key,
	IUser,
	ServiceToken,
	ServiceTokenData
} from '../../models';
import {
	v2PushSecrets as push,
	pullSecrets as pull,
	reformatPullSecrets
} from '../../helpers/secret';
import { pushKeys } from '../../helpers/key';
import { TelemetryService, EventService } from '../../services';
import { eventPushSecrets } from '../../events';

interface V2PushSecret {
	type: string; // personal or shared
	secretKeyCiphertext: string;
	secretKeyIV: string;
	secretKeyTag: string;
	secretKeyHash: string;
	secretValueCiphertext: string;
	secretValueIV: string;
	secretValueTag: string;
	secretValueHash: string;
	secretCommentCiphertext?: string;
	secretCommentIV?: string;
	secretCommentTag?: string;
	secretCommentHash?: string;
}

/**
 * Upload (encrypted) secrets to workspace with id [workspaceId]
 * for environment [environment]
 * @param req
 * @param res
 * @returns
 */
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
	// upload (encrypted) secrets to workspace with id [workspaceId]
	try {
		const postHogClient = await TelemetryService.getPostHogClient();
		let { secrets }: { secrets: V2PushSecret[] } = req.body;
		const { keys, environment, channel } = req.body;
		const { workspaceId } = req.params;

		// validate environment
		const workspaceEnvs = req.membership.workspace.environments;
		if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
			throw new Error('Failed to validate environment');
		}

		// sanitize secrets
		secrets = secrets.filter(
			(s: V2PushSecret) => s.secretKeyCiphertext !== '' && s.secretValueCiphertext !== ''
		);

		await push({
			userId: req.user._id,
			workspaceId,
			environment,
			secrets,
			channel: channel ? channel : 'cli',
			ipAddress: req.ip
		});

		await pushKeys({
			userId: req.user._id,
			workspaceId,
			keys
		});

		if (postHogClient) {
			postHogClient.capture({
				event: 'secrets pushed',
				distinctId: req.user.email,
				properties: {
					numberOfSecrets: secrets.length,
					environment,
					workspaceId,
					channel: channel ? channel : 'cli'
				}
			});
		}

		// trigger event - push secrets
		EventService.handleEvent({
			event: eventPushSecrets({
				workspaceId: new Types.ObjectId(workspaceId),
				environment
			})
		});

	} catch (err) {
		Sentry.setUser({ email: req.user.email });
		Sentry.captureException(err);
		return res.status(400).send({
			message: 'Failed to upload workspace secrets'
		});
	}

	return res.status(200).send({
		message: 'Successfully uploaded workspace secrets'
	});
};

/**
 * Return (encrypted) secrets for workspace with id [workspaceId]
 * for environment [environment]
 * @param req
 * @param res
 * @returns
 */
export const pullSecrets = async (req: Request, res: Response) => {
	let secrets;
	try {
		const postHogClient = await TelemetryService.getPostHogClient();
		const environment: string = req.query.environment as string;
		const channel: string = req.query.channel as string;
		const { workspaceId } = req.params;

		let userId;
		if (req.user) {
			userId = req.user._id.toString();
		} else if (req.serviceTokenData) {
			userId = req.serviceTokenData.user.toString();
		}
		// validate environment
		const workspaceEnvs = req.membership.workspace.environments;
		if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
			throw new Error('Failed to validate environment');
		}

		secrets = await pull({
			userId,
			workspaceId,
			environment,
			channel: channel ? channel : 'cli',
			ipAddress: req.ip
		});

		if (channel !== 'cli') {
			secrets = reformatPullSecrets({ secrets });
		}

		if (postHogClient) {
			// capture secrets pushed event in production
			postHogClient.capture({
				distinctId: req.user.email,
				event: 'secrets pulled',
				properties: {
					numberOfSecrets: secrets.length,
					environment,
					workspaceId,
					channel: channel ? channel : 'cli'
				}
			});
		}
	} catch (err) {
		Sentry.setUser({ email: req.user.email });
		Sentry.captureException(err);
		return res.status(400).send({
			message: 'Failed to pull workspace secrets'
		});
	}

	return res.status(200).send({
		secrets
	});
};

export const getWorkspaceKey = async (req: Request, res: Response) => {
	/* 
    #swagger.summary = 'Return encrypted project key'
    #swagger.description = 'Return encrypted project key'
    
    #swagger.security = [{
        "apiKeyAuth": []
    }]

	#swagger.parameters['workspaceId'] = {
		"description": "ID of project",
		"required": true,
		"type": "string"
	} 

    #swagger.responses[200] = {
        content: {
            "application/json": {
                "schema": { 
                    "type": "array",
                    "items": {
                        $ref: "#/components/schemas/ProjectKey" 
                    },
                    "description": "Encrypted project key for the given project"
                }
            }           
        }
    }   
    */
	let key;
	try {
		const { workspaceId } = req.params;

		key = await Key.findOne({
			workspace: workspaceId,
			receiver: req.user._id
		}).populate('sender', '+publicKey');

		if (!key) throw new Error('Failed to find workspace key');
	} catch (err) {
		Sentry.setUser({ email: req.user.email });
		Sentry.captureException(err);
		return res.status(400).send({
			message: 'Failed to get workspace key'
		});
	}

	return res.status(200).json(key);
}
export const getWorkspaceServiceTokenData = async (
	req: Request,
	res: Response
) => {
	let serviceTokenData;
	try {
		const { workspaceId } = req.params;

		serviceTokenData = await ServiceTokenData
			.find({
				workspace: workspaceId
			})
			.select('+encryptedKey +iv +tag');

	} catch (err) {
		Sentry.setUser({ email: req.user.email });
		Sentry.captureException(err);
		return res.status(400).send({
			message: 'Failed to get workspace service token data'
		});
	}

	return res.status(200).send({
		serviceTokenData
	});
}

/**
 * Return memberships for workspace with id [workspaceId]
 * @param req 
 * @param res 
 * @returns 
 */
export const getWorkspaceMemberships = async (req: Request, res: Response) => {
	/* 
    #swagger.summary = 'Return project memberships'
    #swagger.description = 'Return project memberships'
    
    #swagger.security = [{
        "apiKeyAuth": []
    }]

	#swagger.parameters['workspaceId'] = {
		"description": "ID of project",
		"required": true,
		"type": "string"
	} 

    #swagger.responses[200] = {
        content: {
            "application/json": {
                "schema": { 
                    "type": "object",
					"properties": {
						"memberships": {
							"type": "array",
							"items": {
								$ref: "#/components/schemas/Membership" 
							},
							"description": "Memberships of project"
						}
					}
                }
            }           
        }
    }   
    */
	let memberships;
	try {
		const { workspaceId } = req.params;

		memberships = await Membership.find({
			workspace: workspaceId
		}).populate('user', '+publicKey');
	} catch (err) {
		Sentry.setUser({ email: req.user.email });
		Sentry.captureException(err);
		return res.status(400).send({
			message: 'Failed to get workspace memberships'
		});
	}

	return res.status(200).send({
		memberships
	});
}

/**
 * Update role of membership with id [membershipId] to role [role]
 * @param req 
 * @param res 
 * @returns 
 */
export const updateWorkspaceMembership = async (req: Request, res: Response) => {
	/* 
    #swagger.summary = 'Update project membership'
    #swagger.description = 'Update project membership'
    
    #swagger.security = [{
        "apiKeyAuth": []
    }]

	#swagger.parameters['workspaceId'] = {
		"description": "ID of project",
		"required": true,
		"type": "string"
	} 

	#swagger.parameters['membershipId'] = {
		"description": "ID of project membership to update",
		"required": true,
		"type": "string"
	} 

	#swagger.requestBody = {
      "required": true,
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "properties": {
                "role": {
                    "type": "string",
                    "description": "Role of membership - either admin or member",
                }
            }
          }
        }
      }
    }

    #swagger.responses[200] = {
        content: {
            "application/json": {
                "schema": { 
					"type": "object",
					"properties": {
						"membership": {
							$ref: "#/components/schemas/Membership",
							"description": "Updated membership"
						}
					}
                }
            }           
        }
    }   
    */
	let membership;
	try {
		const {
			membershipId
		} = req.params;
		const { role } = req.body;
		
		membership = await Membership.findByIdAndUpdate(
			membershipId,
			{
				role
			}, {
				new: true
			}
		);
	} catch (err) {
		Sentry.setUser({ email: req.user.email });
		Sentry.captureException(err);
		return res.status(400).send({
			message: 'Failed to update workspace membership'
		});
	}
	
	return res.status(200).send({
		membership
	}); 
}

/**
 * Delete workspace membership with id [membershipId]
 * @param req 
 * @param res 
 * @returns 
 */
export const deleteWorkspaceMembership = async (req: Request, res: Response) => {
	/* 
    #swagger.summary = 'Delete project membership'
    #swagger.description = 'Delete project membership'
    
    #swagger.security = [{
        "apiKeyAuth": []
    }]

	#swagger.parameters['workspaceId'] = {
		"description": "ID of project",
		"required": true,
		"type": "string"
	} 

	#swagger.parameters['membershipId'] = {
		"description": "ID of project membership to delete",
		"required": true,
		"type": "string"
	} 

    #swagger.responses[200] = {
        content: {
            "application/json": {
                "schema": { 
					"type": "object",
					"properties": {
						"membership": {
							$ref: "#/components/schemas/Membership",
							"description": "Deleted membership"
						}
					}
                }
            }           
        }
    }   
    */
	let membership;
	try {
		const { 
			membershipId
		} = req.params;
		
		membership = await Membership.findByIdAndDelete(membershipId);
		
		if (!membership) throw new Error('Failed to delete workspace membership');
		
		await Key.deleteMany({
			receiver: membership.user,
			workspace: membership.workspace
		});
	} catch (err) {
		Sentry.setUser({ email: req.user.email });
		Sentry.captureException(err);
		return res.status(400).send({
			message: 'Failed to delete workspace membership'
		});	
	}
	
	return res.status(200).send({
		membership
	});
}

/**
 * Change autoCapitilzation Rule of workspace
 * @param req
 * @param res
 * @returns
 */
export const toggleAutoCapitalization = async (req: Request, res: Response) => {
	let workspace;
	try {
		const { workspaceId } = req.params;
		const { autoCapitalization } = req.body;

		workspace = await Workspace.findOneAndUpdate(
			{
				_id: workspaceId
			},
			{
				autoCapitalization
			},
			{
				new: true
			}
		);
	} catch (err) {
		Sentry.setUser({ email: req.user.email });
		Sentry.captureException(err);
		return res.status(400).send({
			message: 'Failed to change autoCapitalization setting'
		});
	}

	return res.status(200).send({
		message: 'Successfully changed autoCapitalization setting',
		workspace
	});
};