import { SendMailOptions } from 'nodemailer'
import * as path from 'path'
import * as pug from 'pug'
import { union } from 'ramda'

import { UndefinedError } from '../../models/Error'
import * as Models from '../../models'
import { sendMailExtProd } from '../mailer'
import DBService from './DBService'
import GroupServiceService from './GroupServiceService'

const MAIL_TEMPLATE = path.join(
	__dirname,
	'../../templates/mail/notification.pug'
)

export interface NotificationData {
	shortName: string
	content?: string
	itemId: number
	userId: string
}

type Role = 'purchaser' | 'inspector' | 'requestor' | 'supervisor'
export interface NotificationTarget {
	roles?: Role[]
	groups?: Models.GroupId[]
	users?: string[]
}

interface FindAllOptions {
	userId?: string
}

interface FindAllOptions {
	userId?: string
}

const mailOptions = (
	data: Omit<NotificationData, 'userId'>,
	to: string | string[],
	item: Models.ItemBase
): SendMailOptions => {
	const template = pug.compileFile(MAIL_TEMPLATE)

	let itemName = data.itemId.toString()
	try {
		if (item && item.uid) {
			itemName = item.uid

			if (item.name) {
				itemName += ' - ' + item.name
			}
		}
	} catch {}

	let message = `${data.shortName} ${itemName}`
	if (data.content) {
		message += `: ${data.content}`
	}
	const link =
		'\\\\secheron.cze\\NETLOGON\\Secheron_files\\Corp_Links\\Workflow.lnk'

	return {
		to: Array.isArray(to) ? to.join(', ') : to,
		subject: `${data.shortName}`,
		html: template({ message, link })
	}
}

export default class NotificationService extends DBService<
	Models.Notification
> {
	constructor() {
		super(Models.Notification)
	}

	public async findAll(options?: FindAllOptions) {
		let query = this.Model.query().eager('[item, user]')

		if (options && options.userId) {
			query = query.where('userId', options.userId)

			await Models.User.query()
				.patch({
					hasNotification: false
				})
				.findById(options.userId)
		}

		const notifications = await query
		if (!notifications) {
			throw new UndefinedError()
		}

		return notifications
	}

	public async deleteById(id: number): Promise<void> 


	public async delete(userId: string): Promise<void> {
		await this.Model.query()
			.delete()
			.where('userId', userId)
	}

	public async notify(
		target: NotificationTarget,
		data: Omit<NotificationData, 'userId'>
	): Promise<void> {
		const item = await Models.ItemBase.query()
			.findById(data.itemId)
			.eager({
				itemPrototype: { operations: true },
				itemRequest: true,
				itemService: true,
				watchers: true
			})

		let emails: string[] = []
		if (target.groups) {
			emails = union(emails, await this.groupEmails(target.groups))
		}
		if (target.roles) {
			emails = union(emails, await this.roleEmails(target.roles, item))
		}
		if (target.users) {
			emails = union(emails, await this.userEmails(target.users))
		}

		emails = union(emails, await this.watcherEmails(item))
		emails = union(emails, await this.substituteEmails(emails))

		try {
			let users = await Models.User.query().whereIn('email', emails)

			if (!users) return

			users = users.map(
				u =>
					({
						...u,
						hasNotification: true,
						notifications: [data]
					} as Models.User)
			)

			await Models.User.query().upsertGraph(users, {
				relate: true,
				noDelete: true
			})

			sendMailExtProd(mailOptions(data, emails, item))
		} catch (err) {
			console.log(err)
		}
	}

	private async userEmails(ids: string[]): Promise<string[]> {
		let emails: string[] = []
		try {
			const users = await Models.User.query().whereIn('id', ids)

			if (!users) return emails

			emails = users.filter(u => !!u.email).map(u => u.email)
		} catch (err) {
			console.log(err)
		}

		return emails
	}

	private async groupEmails(groupIds: Models.GroupId[]): Promise<string[]> {
		let emails: string[] = []
		try {
			const groups = await Models.Group.query()
				.whereIn('id', groupIds)
				.eager('[users]')

			if (!groups) return

			emails = groups
				.filter(g => !!g.users)
				.reduce<Models.User[]>(
					(acc, curr) => [...acc, ...curr.users],
					[]
				)
				.filter(u => !!u.email)
				.map(u => u.email)
		} catch (err) {
			console.log(err)
		}

		return emails
	}

	private async roleEmails(
		roles: Role[],
		item: Models.ItemBase
	): Promise<string[]> {
		let users: string[] = []
		for (const role of roles) {
			if (role === 'inspector') {
				users = [item.itemPrototype.currentOperation.inspectorId]
			} else if (role === 'purchaser') {
				if (item.itemPrototype) {
					users = [item.itemPrototype.currentOperation.assigneeId]
				} else if (item.itemRequest) {
					users = [item.itemRequest.purchaserId]
				} else if (item.itemService) {
					users = [item.itemService.purchaserId]
				}
			} else if (role === 'requestor') {
				users = [item.requestorId]
			} else if (role === 'supervisor') {
				if (item.itemRequest) {
					users = [item.itemRequest.supervisorId]
				} else if (item.itemService) {
					try {
						const service = new GroupServiceService()
						users = await service.getUsers(
							item.itemService.supervisorGroupId
						)
					} catch (err) {
						console.log(err)
					}
				}
			}
		}

		const emails = await this.userEmails(users)
		return emails
	}

	private async watcherEmails(item: Models.ItemBase): Promise<string[]> {
		let emails: string[] = []

		try {
			if (!item || !item.watchers) return emails

			emails = item.watchers.filter(w => !!w.email).map(w => w.email)
		} catch (err) {
			console.log(err)
		}

		return emails
	}

	private async substituteEmails(userEmails: string[]): Promise<string[]> {
		let emails: string[] = []
		try {
			const users = await Models.User.query()
				.whereIn('email', userEmails)
				.eager('[substitutes.[substitute]]')

			if (!users) return emails

			emails = users
				.filter(u => !!u.substitutes)
				.reduce<Models.UserSubstitute[]>(
					(acc, curr) => [...acc, ...curr.substitutes],
					[]
				)
				.filter(s => !!s.substitute && !!s.substitute.email && s.notify)
				.map(s => s.substitute.email)
		} catch (err) {
			console.log(err)
		}

		return emails
	}
}

{
		await this.Model.query().deleteById(id)
	}