import { Response, RequestHandler } from 'express'
import { emitPlatformEvent } from '@qelos/api-kit';
import { AuthRequest } from '../../types'
import User from '../models/user'
import UserInternalMetadata from '../models/user-internal-metadata';
import { getEncryptedData, setEncryptedData } from '../services/encrypted-data';
import logger from '../services/logger';
import { isObjectId } from '../../helpers/mongo-utils';
import * as UsersService from '../services/users';
import { Types, Schema } from 'mongoose';

import ObjectId = Types.ObjectId
import { getValidMetadata } from '../services/users';
import Workspace from '../models/workspace';

const privilegedUserFields = 'username phone fullName firstName lastName birthDate roles profileImage socialLogins emailVerified lastLogin';

function getUserIdIfExists(_id, tenant) {
  return User.findOne({ _id, tenant }).select('_id').lean().exec();
}

export function getUsersForAdmin(req: AuthRequest, res: Response): void {
  try {
    // support old versions
    const username = req.query.username?.toString().toLowerCase().trim().replace(/ /g, '+') || undefined;

    const query: any = {
      tenant: req.headers.tenant,
      username: req.query.exact ? username ? new RegExp(username, 'i') : undefined : username ? new RegExp(username, 'i') : undefined,
    };

    if (typeof (req.query as any).roles === 'string') {
      query.roles = { $in: (req.query as any).roles.split(',').map((role: string) => role.trim()) };
    }

    if (req.query._id) {
      query._id = { $in: (req.query._id as string).split(',').map(id => new ObjectId(id.trim())) };
    }

    if (!query.username) {
      delete query.username;
    }

    logger.log('admin db query', query)

    User
      .find(query)
      .select(req.query.select ? req.query.select.toString().replace(/,/g, ' ') : privilegedUserFields)
      .lean()
      .exec()
      .then((users = []) => {
        res.json(users).end();
      })
      .catch((err) => {
        logger.error('failed to load users for admin', req.query, err);
        res.json([]).end();
      })
  } catch (err) {
    logger.error('failed to load users for admin', req.query, err);
    res.json([]).end();
  }
}

export function getUsers(req: AuthRequest, res: Response): RequestHandler {
  if (!req.query.users) {
    getUsersForAdmin(req, res);
    return;
  }

  const users = (req.query.users as string || '')
    .split(',')
    .map(id => {
      const val = id.trim()
      if (isObjectId(val)) {
        return new ObjectId(val)
      }
      return false
    })
    .filter(Boolean)

  if (!users.length) {
    res.status(200)
      .setHeader('x-tenant', req.headers.tenant)
      .setHeader('x-user', req.userPayload?.sub || '-')
      .set('Content-Type', 'application/json').end('[]')
    return;
  }

  User
    .getUsersList(req.headers.tenant, users.filter(Boolean) as any as Schema.Types.ObjectId[], privilegedUserFields)
    .then(list => {
      res.status(200)
        .setHeader('x-tenant', req.headers.tenant)
        .setHeader('x-user', 'p-' + req.userPayload.sub)
        .set('Content-Type', 'application/json').end(list)
    })
    .catch(() => res.status(404).json({ message: 'could not load users' }).end())
}

export function getUser(req: AuthRequest, res: Response): RequestHandler {
  const isPrivileged = !!(req.userPayload && req.userPayload.isPrivileged)

  const promises: Array<Promise<any>> = [
    User.findOne({ _id: req.params.userId, tenant: req.headers.tenant })
      .select(isPrivileged ? privilegedUserFields : 'fullName')
      .lean().exec()
  ]
  if (isPrivileged) {
    promises.push(UserInternalMetadata.findOne({
      user: req.params.userId,
      tenant: req.headers.tenant
    }).lean().exec().catch(() => null))
  }

  Promise.all(promises)
    .then(([user, internalMetadata]) => {
      if (!user) {
        return Promise.reject(null)
      }
      if (isPrivileged) {
        user.internalMetadata = internalMetadata?.metadata || {};
      }
      res.status(200).json(user).end()
    })
    .catch(() => res.status(404).json({ message: 'user not exists' }).end())
  return;
}

export async function getUserEncryptedData(req: AuthRequest, res: Response) {
  const tenant = req.headers.tenant as string;
  if (!tenant) {
    return res.status(401).end();
  }
  try {
    const user = await getUserIdIfExists(req.params.userId, tenant);
    if (!user) {
      throw new Error('user not found');
    }
    const encryptedId = req.headers['x-encrypted-id'];
    const id = user._id + (encryptedId ? ('-' + encryptedId) : '');
    const { value } = await getEncryptedData(tenant, id);

    res.status(200).set('Content-Type', 'application/json').end(value);
  } catch (e) {
    res.status(200).json(null).end()
  }
}

export async function setUserEncryptedData(req: AuthRequest, res: Response) {
  const tenant = req.headers.tenant as string;
  if (!tenant) {
    return res.status(401).end();
  }
  try {
    const user = await getUserIdIfExists(req.params.userId, tenant);
    if (!user) {
      throw new Error('user not found');
    }
    const encryptedId = req.headers['x-encrypted-id'];
    const id = user._id + (encryptedId ? ('-' + encryptedId) : '');
    await setEncryptedData(tenant, id, JSON.stringify(req.body));
    res.status(200).set('Content-Type', 'application/json').end('{}');
  } catch (e) {
    res.status(400).json({ message: 'failed to set encrypted data for user' }).end();
  }
}

export async function createUser(req: AuthRequest, res: Response) {
  const { tenant, name, internalMetadata, metadata, ...userData } = req.body
  const user = new User(userData);

  if (req.authConfig.treatUsernameAs === 'email') {
    user.username = user.username.toLowerCase().trim().replace(/ /g, '+');
    user.email = user.username;
    if (!user.username.includes('@')) {
      res.status(400).json({ message: 'username should be an email address' }).end();
      return;
    }
  }

  user.tenant = req.headers.tenant;
  if (!user.fullName && name) {
    user.fullName = name;
  }
  if (metadata) {
    try {
      user.metadata = getValidMetadata(metadata, req.authConfig.additionalUserFields);
    } catch {
      // nothing
    }
  }
  try {
    if (!user.tenant) {
      throw new Error('tenant is missing');
    }
    const { _id, fullName, firstName, lastName, birthDate, username, roles } = await user.save()
    const userInternalMetadata = new UserInternalMetadata({
      tenant: req.headers.tenant,
      user: _id,
      metadata: internalMetadata || {}
    });
    await userInternalMetadata.save();

    const response = { _id, name: fullName, firstName, lastName, birthDate, username, roles, internalMetadata };

    emitPlatformEvent({
      tenant: req.headers.tenant,
      user: _id as string,
      source: 'auth',
      kind: 'users',
      eventName: 'user-created',
      description: 'user created by admin endpoint',
      metadata: {
        user: response
      }
    })

    res.status(200).json(response).end()
  } catch (e) {
    logger.log('internal user create error', e);
    res.status(400).json({ message: 'user creation failed' }).end()
  }
}

export async function updateUser(req: AuthRequest, res: Response) {
  const {
    username,
    roles,
    name,
    password,
    fullName,
    firstName,
    lastName,
    birthDate,
    profileImage,
    internalMetadata
  } = req.body || {}
  let metadata;
  let newInternalMetadata;
  try {
    if (req.body?.metadata) {
      metadata = getValidMetadata(req.body?.metadata, req.authConfig.additionalUserFields);
    }
  } catch {
    metadata = null;
  }
  try {
    await UsersService.updateUser(
      { _id: req.params.userId, tenant: req.headers.tenant },
      { username, roles, password, fullName: fullName || name, firstName, lastName, birthDate, metadata, profileImage },
      req.authConfig
    )
    if (internalMetadata) {
      const userInternalMetadata = (await UserInternalMetadata.findOne({
        user: req.params.userId,
        tenant: req.headers.tenant
      }).exec()) || new UserInternalMetadata({
        user: req.params.userId,
        tenant: req.headers.tenant
      })
      newInternalMetadata = userInternalMetadata.metadata = Object.assign(userInternalMetadata.metadata || {}, internalMetadata);
      userInternalMetadata.markModified('metadata')
      await userInternalMetadata.save();
    }

    const response = {
      username,
      name: fullName || name,
      fullName: fullName || name,
      firstName,
      lastName,
      birthDate,
      profileImage,
      roles,
      internalMetadata: newInternalMetadata || internalMetadata,
      _id: req.params.userId
    };

    emitPlatformEvent({
      tenant: req.headers.tenant,
      user: req.params.userId,
      source: 'auth',
      kind: 'users',
      eventName: 'user-updated',
      description: 'user updated by admin endpoint',
      metadata: {
        user: response
      }
    })

    res.status(200).json(response).end()
  } catch (e) {
    res.status(500).json({ message: 'user update failed', info: e?.info }).end()
  }
}

export async function removeUser(req: AuthRequest, res: Response) {
  try {
    await UsersService.deleteUser(req.params.userId, req.headers.tenant);

    emitPlatformEvent({
      tenant: req.headers.tenant,
      user: req.params.userId,
      source: 'auth',
      kind: 'users',
      eventName: 'user-removed',
      description: 'user removed by admin endpoint',
      metadata: null
    })

    res.status(200).json({ _id: req.params.userId, tenant: req.headers.tenant }).end()
  } catch (e) {
    res.status(400).json({ message: 'user deletion failed' }).end()
  }
}

export async function getUsersStats(req: AuthRequest, res: Response) {
  try {
    const [users, workspaces] = await Promise.all([
      User.countDocuments({ tenant: req.headers.tenant }).exec(),
      Workspace.countDocuments({ tenant: req.headers.tenant }).exec()
    ]);
    res.status(200).json({ users, workspaces }).end();
  } catch (e) {
    res.status(500).json({ message: 'could not load users stats' }).end();
  }
}