const crypto = require('crypto')
const express = require('express')
const multer = require('multer')
const db = require('../include/db')
const bot = require('./bot')
const fs = require('fs')
const cookieParser = require('cookie-parser')

const app = express.Router()
const upload = multer({
	storage: multer.memoryStorage(),
	limits: {
		fileSize: 1_000_000  // 1mb
	}
})

const sessions = {}
const emailCache = {} // key = email, value = code

app.use((req, res, next) => {
	if (req.path == '/auth/email' || req.path == '/auth/telegram' || req.path == '/auth/register' || req.path == '/auth/logout')
		return next()

	const asid = req.query.asid || req.cookies.asid
	req.session = sessions[asid]
	if (!req.session) 
		throw Error('ACCESS_DENIED::401')

	res.locals.customer_id = req.session.customer_id
	next()
})

// AUTH
function createSession(req, res, customer_id) {
	if (!customer_id)
		throw Error('AUTH_ERROR::500')

	res.locals.customer_id = customer_id
	const asid = crypto.randomBytes(64).toString('hex')
	req.session = sessions[asid] = {asid, customer_id }
	res.setHeader('Set-Cookie', [`asid=${asid};httpOnly;path=/api/admin`])	
}

app.post('/auth/email', (req, res, next) => {
	res.locals.email = req.body?.email
	res.locals.password = req.body?.password
	 
	const customer_id = db
		.prepare(`select id from customers  where is_blocked = 0 and email = :email and password is not null and password = :password `)
		.pluck(true)
		.get(res.locals)

	if (!customer_id) 
		throw Error('AUTH_ERROR::401')

	createSession(req, res, customer_id)
	res.status(200).json({success: true})
})

app.post('/auth/telegram', (req, res, next) => {
	let customer_id = db
		.prepare(`select id from customers where is_blocked = 0 and telegram_id = :telegram_id`)
		.pluck(true)
		.get(res.locals) || db
		.prepare(`replace into customers (telegram_id, is_blocked) values (:telegram_id, 0) returning id`)
		.pluck(true)
		.get(res.locals)		

	createSession(req, res, customer_id)
	res.status(200).json({success: true})
})

app.get('/auth/logout', (req, res, next) => {
	if (req.session?.asid)
		delete sessions[req.session.asid]
	res.setHeader('Set-Cookie', [`asid=; expired; httpOnly;path=/api/admin`])
	res.status(200).json({success: true})
})

app.post('/auth/register', (req, res, next) => {
	const email = String(req.body.email ?? '').trim()
	const code = String(req.body.code ?? '').trim()
	const password = String(req.body.password ?? '').trim()
  
	if (email) {
	  const validateEmail = email => String(email).toLowerCase().match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/)
	  if (!validateEmail(email)) 
		throw Error('INCORRECT_EMAIL::400')
  
	  const customer_id = db
		.prepare('select id from customers where email = :email')
		.pluck(true)
		.get({email}) 
  
	  if (customer_id)   
		throw Error('USED_EMAIL::400')
	}
  
	if (email && !code) {
	  const code = Math.random().toString().substr(2, 4) 
	  emailCache[email] = code
	  // To-Do: send email
	  console.log(`${email} => ${code}`)
	}
  
	if (email && code && !password) {
	  if (emailCache[email] != code)
		throw Error('INCORRECT_CODE::400')
	}
  
	if (email && code && password) {
	  if (password.length < 8)
		throw Error('INCORRECT_PASSWORD::400')
  
	  db
		.prepare('insert into customers (email, password, is_blocked) values (:email, :password, 0)')
		.run({email, password}) 
	}
  
	res.status(200).json({success: true})      
  })


// CUSTOMER
app.get('/customer/profile', (req, res, next) => {
	const row = db
		.prepare(`
			select id, name, email, plan, coalesce(json_balance, '{}') json_balance, coalesce(json_company, '{}') json_company, upload_group_id 
			from customers 
			where id = :customer_id
		`)
		.get(res.locals) 

	if (row?.upload_group_id) {
		row.upload_group = db
			.prepare(`select id, name, telegram_id from groups where id = :group_id and project_id is null`)
			.safeIntegers(true)
			.get({ group_id: row.upload_group_id})  
		delete row.upload_group_id
	}

	for (const key in row) {
		if (key.startsWith('json_')) {
			row[key.substr(5)] = JSON.parse(row[key])
			delete row[key]
		}
	}
	
	res.status(200).json({success: true, data: row})
})

app.put('/customer/profile', (req, res, next) => {
	if (req.body.company instanceof Object) 
		req.body.json_company = JSON.stringify(req.body.company) 

	const info = db
		.prepareUpdate(
			'customers', 
			['name', 'password', 'json_company'], 
			req.body, 
			['id'])
		.run(Object.assign({}, req.body, {id: req.session.customer_id}))
	
	if (info.changes == 0) 
		throw Error('NOT_FOUND::404')

	res.status(200).json({success: true})
})

// PROJECT
app.get('/project', (req, res, next) => {
	const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : ''

	const rows = db
		.prepare(`
			select id, name, description, logo 
			  from projects 
			 where customer_id = :customer_id ${where} and is_deleted <> 1
			 order by name
		`)
		.all(res.locals)

	if (where && rows.length == 0)
		throw Error('NOT_FOUND::404')

	res.status(200).json({success: true, data: where ? rows[0] : rows})
})

app.get('/project/:pid(\\d+)', (req, res, next) => {
	res.redirect(req.baseUrl + `/project?id=${req.params.pid}`)
})

app.post('/project', (req, res, next) => {
	res.locals.name = req.body?.name
	res.locals.description = req.body?.description
	res.locals.logo = req.body?.logo

	const id = db
		.prepare(`
			insert into projects (customer_id, name, description, logo) 
			values (:customer_id, :name, :description, :logo) 
			returning id
		`)
		.pluck(true)
		.get(res.locals)

	res.status(200).json({success: true, data: id})
})

app.put('/project/:pid(\\d+)', (req, res, next) => {
	res.locals.id = req.params.pid
	res.locals.name = req.body?.name
	res.locals.description = req.body?.description
	res.locals.logo = req.body?.logo

	const info = db
		.prepareUpdate(
			'projects', 
			['name', 'description', 'logo'], 
			res.locals, 
			['id', 'customer_id'])
		.run(res.locals)
	
	if (info.changes == 0)
		throw Error('NOT_FOUND::404')

	res.status(200).json({success: true})
})

app.delete('/project/:pid(\\d+)', async (req, res, next) => {
	res.locals.id = req.params.pid

	const info = db
		.prepare('update projects set id_deleted = 1 where id = :id and customer_id = :customer_id')
		.run(res.locals)

	if (info.changes == 0)
		throw Error('NOT_FOUND::404')

	const groupIds = db
		.prepare(`select id from groups where project_id = :id`)
		.pluck(true)
		.all(res.locals)

	for (const groupId of groupIds) {
		await bot.sendMessage(groupId, 'Проект удален')
		await bot.leaveGroup(groupId)
	}
	
	db.prepare(`updates groups set project_id = null where id in (${ groupIds.join(', ')})`).run()

	res.status(200).json({success: true})
})

app.use ('/project/:pid(\\d+)/*', (req, res, next) => {
	res.locals.project_id = parseInt(req.params.pid) 

	const row = db
		.prepare('select 1 from projects where id = :project_id and customer_id = :customer_id and is_deleted <> 1')
		.get(res.locals)

	if (!row)
		throw Error('ACCESS_DENIED::401')

	next()
})

// USER
app.get('/project/:pid(\\d+)/user', (req, res, next) => {
	const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : ''

	const rows = db
		.prepare(`
			select u.id, u.telegram_id, u.firstname, u.lastname, u.username, u.photo,
			       ud.fullname, ud.role, ud.department, ud.is_blocked
			  from users u
			       left join user_details ud on u.id = ud.user_id and ud.project_id = :project_id
			 where id in (
				select user_id 
				from group_users 
				where group_id in (select id from groups where project_id = :project_id)
				) ${where}
		`)
		.safeIntegers(true)
		.all(res.locals) 

	if (where && rows.length == 0)
		throw Error('NOT_FOUND::404')

	res.status(200).json({success: true, data: where ? rows[0] : rows})
})

app.get('/project/:pid(\\d+)/user/:uid(\\d+)', (req, res, next) => {
	res.redirect(req.baseUrl + `/project/${req.params.pid}/user?id=${req.params.uid}`)
})

app.put('/project/:pid(\\d+)/user/:uid(\\d+)', (req, res, next) => {
	res.locals.user_id = parseInt(req.params.uid)
	
	res.locals.fullname = req.body?.fullname
	res.locals.role = req.body?.role
	res.locals.department = req.body?.department
	res.locals.is_blocked = req.body?.is_blocked

	const info = db
		.prepareUpdate('user_details', 
			['fullname', 'role', 'department', 'is_blocked'], 
			res.locals, 
			['user_id', 'project_id']
		) 
		.all(res.locals) 

	if (info.changes == 0)
		throw Error('NOT_FOUND::404')	
	
	res.status(200).json({success: true})
})

app.get('/project/:pid(\\d+)/token', (req, res, next) => {
	res.locals.time = Math.floor(Date.now() / 1000)

	const key = db
		.prepare('select generate_key(id, :time) from projects where id = :project_id and customer_id = :customer_id')
		.pluck(true)
		.get(res.locals)

	if (!key)
		throw Error('NOT_FOUND::404')

	res.status(200).json({success: true, data: key})		  
})

// COMPANY
app.get('/project/:pid(\\d+)/company', (req, res, next) => {
	const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : ''

	const rows = db
		.prepare(`
			select id, name, email, phone, description, logo,
			(select json_group_array(user_id) from company_users where company_id = c.id) users
			from companies c 
			where project_id = :project_id ${where}
			order by name
		`)
		.all(res.locals)

	rows.forEach(row => row.users = JSON.parse(row.users || '[]'))

	if (where && rows.length == 0)
		throw Error('NOT_FOUND::404')

	res.status(200).json({success: true, data: where ? rows[0] : rows})
})

app.get('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => {
	res.redirect(req.baseUrl + `/project/${req.params.pid}/company?id=${req.params.cid}`)
})

app.post('/project/:pid(\\d+)/company', (req, res, next) => {
	res.locals.name = req.body?.name
	res.locals.email = req.body?.email
	res.locals.phone = req.body?.phone
	res.locals.site = req.body?.site
	res.locals.description = req.body?.description
	res.locals.logo = req.body?.logo

	const id = db
		.prepare(`
			insert into companies (project_id, name, email, phone, site, description, logo) 
			values (:project_id, :name, :email, :phone, :site, :description, :logo) 
			returning id
		`)
		.pluck(res.locals)
		.get(res.locals)

	res.status(200).json({success: true, data: id})
})

app.put('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => {
	res.locals.id = parseInt(req.params.cid)
	res.locals.name = req.body?.name
	res.locals.email = req.body?.email
	res.locals.phone = req.body?.phone
	res.locals.site = req.body?.site
	res.locals.description = req.body?.description

	const info = db
		.prepareUpdate(
			'companies', 
			['name', 'email', 'phone', 'site', 'description'], 
			res.locals, 
			['id', 'project_id'])
		.run(res.locals)
	
	if (info.changes == 0)
		throw Error('NOT_FOUND::404')

	res.status(200).json({success: true})
})

app.delete('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => {
	res.locals.company_id = parseInt(req.params.cid)

	const info = db
		.prepare(`delete from companies where id = :company_id and project_id = :project_id`)
		.run(res.locals)	

	if (info.changes == 0)
		throw Error('NOT_FOUND::404')

	res.status(200).json({success: true})
})

app.get('/project/:pid(\\d+)/group', (req, res, next) => {
	const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : ''

	const rows = db
		.prepare(`
			select  id, name, telegram_id, is_channel, user_count, bot_can_ban
			from groups 
			where project_id = :project_id ${where}
		`)
		.all(res.locals)

	if (where && rows.length == 0)
		throw Error('NOT_FOUND::404')

	res.status(200).json({success: true, data: where ? rows[0] : rows})
})

app.get('/project/:pid(\\d+)/group/:gid(\\d+)', (req, res, next) => {
	res.redirect(req.baseUrl + `/project/${req.params.pid}/group?id=${req.params.uid}`)
})

app.delete('/project/:pid(\\d+)/group/:gid(\\d+)', async (req, res, next) => {
	res.locals.group_id = parseInt(req.params.gid)	
	const info = db
		.prepare(`update groups set project_id = null where id = :group_id and project_id = :project_id`)
		.run(res.locals)

	if (info.changes == 0)
		throw Error('NOT_FOUND::404')

	await bot.sendMessage(res.locals.group_id, 'Группа удалена из проекта')

	res.status(200).json({success: true})
})

app.put('/project/:pid(\\d+)/company/:cid(\\d+)/user', (req, res, next) => {
	res.locals.company_id = parseInt(req.params.cid)

	// Проверка, что есть доступ к компании
	const row = db
		.prepare('select 1 from companies where id = :company_id and project_id = :project_id')
		.get(res.locals)

	if (!row)	
		throw Error('NOT_FOUND::404')

	const user_ids = req.body instanceof Array ? [...new Set(req.body.map(e => parseInt(e)))] : []

	// Проверка, что пользователи имеют доступ к проекту
	let rows = db
		.prepare(`
			select user_id 
			  from group_users 
			 where group_id in (select id from groups where project_id = :project_id)
		`)
		.pluck(true) // .raw?
		.get(res.locals)

	if (user_ids.some(user_id => !rows.contains(user_id)))
		throw Error('INACCESSABLE_MEMBER::400')

	// Проверка, что пользователи не участвуют в других компаниях на проекте
	rows = db
		.prepare(`
			select user_id 
			  from company_users 
			 where company_id in (select id from companies where id <> :company_id and project_id = :project_id)
		`)
		.pluck(true) // .raw?
		.get(res.locals)

	if (user_ids.some(user_id => !rows.contains(user_id)))
		throw Error('USED_MEMBER::400')

	
	db
		.prepare(`delete from company_users where company_id = :company_id`)
		.run(res.locals)

	db
		.prepare(`
			insert into company_users (company_id, user_id) 
			select :company_id, value from json_each(:json_ids)
		`)
		.run(res.locals, {json_ids: JSON.stringify(user_ids)})

	res.status(200).json({success: true})
})

module.exports = app