import * as Sentry from '@sentry/node';
import { Request, Response } from 'express';
import crypto from 'crypto';
import bcrypt from 'bcrypt';
import {
    User,
    ServiceAccount,
    ServiceTokenData
} from '../../models';
import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions';
import { 
    PERMISSION_READ_SECRETS,
    AUTH_MODE_JWT,
    AUTH_MODE_SERVICE_ACCOUNT,
    AUTH_MODE_SERVICE_TOKEN
} from '../../variables';
import { getSaltRounds } from '../../config';
import { BadRequestError } from '../../utils/errors';

/**
 * Return service token data associated with service token on request
 * @param req 
 * @param res 
 * @returns 
 */
export const getServiceTokenData = async (req: Request, res: Response) => {
    /* 
    #swagger.summary = 'Return Infisical Token data'
    #swagger.description = 'Return Infisical Token data'
    
    #swagger.security = [{
        "bearerAuth": []
    }]

    #swagger.responses[200] = {
        content: {
            "application/json": {
                "schema": { 
                    "type": "object",
					"properties": {
                        "serviceTokenData": {
                            "type": "object",
                            $ref: "#/components/schemas/ServiceTokenData",
                            "description": "Details of service token"
                        }
					}
                }
            }           
        }
    }   
    */

    if (!(req.authData.authPayload instanceof ServiceTokenData)) throw BadRequestError({
        message: 'Failed accepted client validation for service token data'
    });

    const serviceTokenData = await ServiceTokenData
        .findById(req.authData.authPayload._id)
        .select('+encryptedKey +iv +tag')
        .populate('user');

    return res.status(200).json(serviceTokenData);
}

/**
 * Create new service token data for workspace with id [workspaceId] and
 * environment [environment].
 * @param req 
 * @param res 
 * @returns 
 */
export const createServiceTokenData = async (req: Request, res: Response) => {
    let serviceTokenData;

    const {
        name,
        workspaceId,
        environment,
        encryptedKey,
        iv,
        tag,
        expiresIn,
        permissions
    } = req.body;

    const secret = crypto.randomBytes(16).toString('hex');
    const secretHash = await bcrypt.hash(secret, await getSaltRounds());

    let expiresAt; 
    if (expiresIn) {
        expiresAt = new Date()
        expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
    }

    let user, serviceAccount;
    
    if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User) {
        user = req.authData.authPayload._id;
    }

    if (req.authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && req.authData.authPayload instanceof ServiceAccount) {
        serviceAccount = req.authData.authPayload._id;
    }
        
    serviceTokenData = await new ServiceTokenData({
        name,
        workspace: workspaceId,
        environment,
        user,
        serviceAccount,
        lastUsed: new Date(),
        expiresAt,
        secretHash,
        encryptedKey,
        iv,
        tag,
        permissions
    }).save();

    // return service token data without sensitive data
    serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id);

    if (!serviceTokenData) throw new Error('Failed to find service token data');

    const serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;

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

/**
 * Delete service token data with id [serviceTokenDataId].
 * @param req 
 * @param res 
 * @returns 
 */
export const deleteServiceTokenData = async (req: Request, res: Response) => {
    const { serviceTokenDataId } = req.params;

    const serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);

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