const util = require('util')
const crypto = require('crypto')
const EventEmitter = require('events')
const fs = require('fs')
const db = require('../include/db')

const { Api, TelegramClient } = require('telegram')
const { StringSession } = require('telegram/sessions')
const { NewMessage } = require('telegram/events')
const { Button } = require('telegram/tl/custom/button')
const { CustomFile } = require('telegram/client/uploads')

//const session = new StringSession('1AgAOMTQ5LjE1NC4xNjcuNTABu2OaFuD5Oyi5wGck+n5ldAfshzYfwlWee+OUxYBvFzlKAdW11Hsndu1SJBLUnKjP8sTJEPbLwdqANBhBXmQMghLVAblwK6TxLfsWxy2zf/HGLeNXohhrsep0hBxu9imyHV6OI6gQG+c5qaGkzjZrz0AcS4ut0xy99XrXgjiNfnjeMX7a0mOk6IK9iKdwbX9kXTfclFLVppiBGXolYJjVb2E57tk4+7RncIVyw+Fxn0NZfnhEfHJZly6j03arZOeM5VYl9ul8+3lJDD+KJJHeMgImmYjmcFcF3CbtkhPuTSPnWKtCnm2sRzepn5VFfoG6zgYff04fBdKGvHAai+wQSOY=')
const session = new StringSession('1AgAOMTQ5LjE1NC4xNjcuNTEBuzSgmBQR5/m8M8cyOnsLCIOkYQJTizJoJRZiPKK+eBjMuodc0JuKQwzeWBRJI/c6YxaBHvokpngf5kr57uly+meSPPlFq6MyoSSQDbEJ3VAAWJu+/ALN0ickE92RjRfM5Kw6DimC9FXuMgJJsoUHtk/i+ZGXy9JB+q67G0yy8NvFIuWpFHJDkwmi0qTlTgJ5UOm4PYkV01iNUcV5siaWFVTTLsetHtBUdMOzg5WjjvuOyYV/MIx+z7ynhvF3DxLPCugxqhCvZ/RW+0vldrTX5TZ0BzIDk2eNFQjRORJcZo6upwvH7aZYStV4DxhIi1dEYu5gyvnt4vkbR5kuvE/GqO0=')


let client

function registerUser (telegramId) {
	db
		.prepare(`insert or ignore into users (telegram_id) values (:telegram_id)`)
		.safeIntegers(true)
		.run({ telegram_id: telegramId })

	return db
		.prepare(`select id from users where telegram_id = :telegram_id`)
		.safeIntegers(true)
		.pluck(true)
		.get({ telegram_id: telegramId })
}

function updateUser (userId, data) {
	const info = db
		.prepare(`
			update users set firstname = :firstname, lastname = :lastname, username = :username, 
			access_hash = :access_hash, language_code = :language_code where id = :user_id
		`)
		.safeIntegers(true)
		.run({
			user_id: userId,
			firstname: data.firstName,
			lastname: data.lastName,
			username: data.username,
			access_hash: data.accessHash.value,
			language_code: data.langCode
		})

	return info.changes == 1
}

async function updateUserPhoto (userId, data) {
	const photoId = data.photo?.photoId?.value
	if (!photoId) 
		return

	const tgUserId = db
		.prepare(`select telegram_id from users where id = :user_id and coalesce(photo_id, 0) <> :photo_id`)
		.safeIntegers(true)
		.pluck(true)
		.get({user_id: userId, photo_id: photoId })

	if (!tgUserId)
		return

	const photo = await client.invoke(new Api.photos.GetUserPhotos({
		userId: new Api.PeerUser({ userId: tgUserId }),
		maxId: photoId,
		offset: -1,
		limit: 1,
	}));

	const file = await client.downloadFile(new Api.InputPhotoFileLocation({
		id: photoId,
		accessHash: photo.photos[0]?.accessHash,
		fileReference: Buffer.from('random'),
		thumbSize: 'a',
	}, {}))	

	db
		.prepare(`update users set photo_id = :photo_id, photo = :photo where id = :user_id`)
		.safeIntegers(true)
		.run({ user_id: userId, photo_id: photoId, photo: file.toString('base64') })
}

async function registerGroup (telegramId, isChannel) {
	db
		.prepare(`insert or ignore into groups (telegram_id, is_channel) values (:telegram_id, :is_channel)`)
		.safeIntegers(true)
		.run({ telegram_id: telegramId, is_channel: +isChannel })

	const row = db
		.prepare(`select id, name from groups where telegram_id = :telegram_id`)
		.safeIntegers(true)
		.get({telegram_id: telegramId}) 

	if (!row?.name) {	
		const entity = isChannel ? { channelId: telegramId } : { chatId: telegramId }
		const group = await client.getEntity(isChannel ? new Api.PeerChannel(entity) : new Api.PeerChat(entity))

		db
			.prepare(`update groups set name = :name where id = :group_id`)
			.run({ group_id: row.id, name: group.title })
	}

	return row.id
}

async function attachGroup(groupId, isChannel, projectId) {
	const info = db
		.prepare(`update groups set project_id = :project_id where id = :group_id and coalesce(project_id, 1) = 1`)
		.run({ group_id: groupId, project_id: projectId })

	if (info.changes == 1) {
		const inputPeer = isChannel ?
			new Api.InputPeerChannel({ channelId: tgGroupId }) :
			new Api.InputPeerChat({ chatlId: tgGroupId })

		const query = `select (select name from customers where id = p.customer_id) || ' >> ' || p.name from projects p where id = :project_id`
		const message = db
			.prepare(query)
			.pluck(true)
			.get({project_id: projectId})
		if (message)
			await client.sendMessage(inputPeer, {message})
	}

	return info.changes == 1
}

async function onGroupAttach (tgGroupId, isChannel) {
	const projectId = db
		.prepare(`select project_id from groups where telegram_id = :telegram_id`)
		.safeIntegers(true)
		.pluck(true)
		.get({ telegram_id: tgGroupId })

	const entity =  isChannel ? { channelId: tgGroupId } : { chatId: tgGroupId }
	const inputPeer = await client.getEntity( isChannel ?
		new Api.InputPeerChannel(entity) :
		new Api.InputPeerChat(entity)
	)
		
	const resultBtn = await client.sendMessage(inputPeer, {
		message: 'ReadyOrNot',
		buttons: client.buildReplyMarkup([[Button.url('Открыть проект', 'https://t.me/ready_or_not_2025_bot/userapp?startapp=user_' + projectId)]])
	})

	await client.invoke(new Api.messages.UpdatePinnedMessage({
		peer: inputPeer,
		id: resultBtn.id,
		unpin: false
	}))

//fs.appendFileSync('./1.log', '\n>' + tgGroupId + ':' + isChannel + '<\n')
}

async function reloadGroupUsers(groupId, onlyReset) {
	db
		.prepare(`delete from group_users where group_id = :group_id`)
		.run({ group_id: groupId })

	if (onlyReset)
		return

	const group = db
		.prepare(`select telegram_id, is_channel, access_hash from groups where id = :group_id`)
		.get({ group_id: groupId})

	console.log (123, group)

	if (!group)
		return
	
	const tgGroupId = group.telegram_id
	const isChannel = group.is_channel
	let accessHash = group.access_hash

	console.log ('HERE')
	
	db
		.prepare(`update groups set access_hash = :access_hash where id = :group_id`)
		.safeIntegers(true)
		.run({
			group_id: groupId,
			access_hash: accessHash,
		})

	const result = isChannel ?
		await client.invoke(new Api.channels.GetParticipants({
			channel: new Api.PeerChannel({ channelId: tgGroupId }),
			filter: new Api.ChannelParticipantsRecent(),
			limit: 999999,
			offset: 0
	        })) : await client.invoke(new Api.messages.GetFullChat({
			chatId: tgGroupId,
		}))

	const users = result.users.filter(user => !user.bot)
	for (const user of users) {
		const userId = registerUser(user.id.value, user)

		if (updateUser(userId, user)) {
			await updateUserPhoto (userId, user)

			const query = `insert or ignore into group_users (group_id, user_id) values (:group_id, :user_id)`
			db.prepare(query).run({ group_id: groupId, user_id: userId })
		}
	}
}

async function registerUpload(data) {
	if (!data.projectId || !data.media)
		return false

	const uploadGroup = db
		.prepare(`
			select id, telegram_id, project_id, is_channel, access_hash 
			  from groups 
			 where id = (select upload_group_id 
				       from customers 
			              where id = (select customer_id from projects where id = :project_id limit 1)
				      limit 1) 
			limit 1
		`)
		.safeIntegers(true)
		.get({project_id: data.projectId})

	if (!uploadGroup || !uploadGroup.telegram_id || uploadGroup.id == data.originGroupId)
		return false

	const tgUploadGroupId = uploadGroup.telegram_id

	const peer = uploadGroup.is_channel ?
		new Api.PeerChannel({ channelId: tgUploadGroupId }) :
		new Api.PeerChat({ chatlId: tgUploadGroupId })

	let resultId = 0

	try {
		const result = await client.invoke(new Api.messages.SendMedia({
				peer,
				media: data.media,
				message: data.caption || '',
				background: true,
				silent: true
		}))

		const update = result.updates.find(u => 
			(u.className == 'UpdateNewMessage' || u.className == 'UpdateNewChannelMessage') && 
			u.message.className == 'Message' && 
			(u.message.peerId.channelId?.value == tgUploadGroupId || u.message.peerId.chatId?.value == tgUploadGroupId) &&
			u.message.media)

		const udoc = update?.message?.media?.document
		if (udoc) {
			resultId = db
				.prepare(`
					insert into documents (project_id, origin_group_id, origin_message_id, group_id, message_id, 
					file_id, access_hash, filename, mime, caption, size, published_by, parent_type, parent_id)
					values (:project_id, :origin_group_id, :origin_message_id, :group_id, :message_id, 
					:file_id, :access_hash, :filename, :mime, :caption, :size, :published_by, :parent_type, :parent_id)
					returning id
				`)
				.safeIntegers(true)
				.pluck(true)
				.get({
					project_id: data.projectId,
					origin_group_id: data.originGroupId,
					origin_message_id: data.originMessageId,
					group_id: uploadGroup.id,
					message_id: update.message.id,
					file_id: udoc.id.value,
					filename: udoc.attributes.find(attr => attr.className == 'DocumentAttributeFilename')?.fileName,
					access_hash: udoc.accessHash.value,
					mime: udoc.mimeType,
					caption: data.caption,
					size: udoc.size.value,
					published_by: data.publishedBy,
					parent_type: data.parentType,
					parent_id: data.parentId
				})
		}

	} catch (err) {
		fs.appendFileSync('./1.log', '\n\nERR:' + err.message + ':' + JSON.stringify (err.stack)+'\n\n')
		console.error('Message.registerUpload: ' + err.message)
	}

	return resultId
}

async function onNewServiceMessage (msg, isChannel) {
	const action = msg.action || {}
	const tgGroupId = isChannel ? msg.peerId?.channelId?.value : msg.peerId?.chatId?.value
	const groupId = await registerGroup(tgGroupId, isChannel)	

	// Group/Channel rename
	if (action.className == 'MessageActionChatEditTitle') {	
		const info = db
			.prepare(`
				update groups 
				set name = :name, is_channel = :is_channel, last_update_time = :last_update_time 
				where telegram_id = :telegram_id
			`)
			.safeIntegers(true)
			.run({
				name: action.title,
				is_channel: +isChannel, 
				last_update_time: Math.floor (Date.now() / 1000),
		 		telegram_id: tgGroupId
			})
	}

	// Chat to Channel
	if (action.className == 'MessageActionChatMigrateTo') {
		const info = db
			.prepare(`
				update groups 
				set telegram_id = :new_telegram_id, name = :name, is_channel = 1, last_update_time = :last_update_time 
				where telegram_id = :old_telegram_id
			`)
			.safeIntegers(true)
			.run({
				name: action.title,
				last_update_time: Date.now() / 1000,
		 		old_telegram_id: tgGroupId,
				new_telegram_id: action.channelId.value
			})
	}

	// User/s un/register
	if (action.className == 'MessageActionChatAddUser' || action.className == 'MessageActionChatDeleteUser' ||
		action.className == 'MessageActionChannelAddUser' || action.className == 'MessageActionChannelDeleteUser'
		) {

		const tgUserIds = [action.user, action.users, action.userId].flat().filter(Boolean).map(e => BigInt(e.value))
		const isAdd = action.className == 'MessageActionChatAddUser' || action.className == 'MessageActionChannelAddUser'

		if (tgUserIds.indexOf(bot.id) == -1) {
			// Add/remove non-bot users
			for (const tgUserId of tgUserIds) {
				const userId = registerUser(tgUserId)

				if (isAdd) {
					try {
						const user = await client.getEntity(new Api.PeerUser({ userId: tgUserId }))
						updateUser(userId, user)
						await updateUserPhoto (userId, user)
					} catch (err) {
						console.error(msg.className + ', ' + userId + ': ' + err.message)
					}						
				}

				const query = isAdd ? 
					`insert or ignore into group_users (group_id, user_id) values (:group_id, :user_id)` :
					`delete from group_users where group_id = :group_id and user_id = :user_id`
				db.prepare(query).run({ group_id: groupId, user_id: userId })
			}
		}
	}
}

async function onNewMessage (msg, isChannel) {
	const tgGroupId = isChannel ? msg.peerId?.channelId?.value : msg.peerId?.chatId?.value
	const groupId = await registerGroup(tgGroupId, isChannel)

	// Document is detected
	if (msg.media?.document) {
		const doc = msg.media.document

		const projectId = db
			.prepare(`select project_id from groups where telegram_id = :telegram_id`)
			.safeIntegers(true)
			.pluck(true)
			.get({telegram_id: tgGroupId})

		const media = new Api.InputMediaDocument({
			id: new Api.InputDocument({
					id: doc.id.value, 
					accessHash: doc.accessHash.value, 
					fileReference: doc.fileReference
				})
			})

		await registerUpload({
			projectId,
			media,
			caption: msg.message,
			originGroupId: groupId,
			originMessageId: msg.id,
			parentType: 0,
			publishedBy: registerUser (msg.fromId?.userId?.value)
		})
	}

	if (msg.message?.startsWith('KEY-')) {
		let projectName = db
				.prepare(`
					select name 
					from projects 
					where id in (
						select project_id from groups where id = :group_id
						union
						select id from projects where upload_group_id = :group_id)
				`)
				.pluck(true)
				.get({ group_id: groupId })

		if (projectName) 
			return await bot.sendMessage(groupId, 'Группа уже используется на проекте ' + projectName)

		const [_, time64, key] = msg.message.substr(3).split('-')
		const now = Math.floor(Date.now() / 1000)
		const time = Buffer.from(time64, 'base64')

		if (now - 3600 >= time && time >= now) 
			return await bot.sendMessage(groupId, 'Время действия ключа для привязки истекло')

		const projectId = db
			.prepare(`select id from projects where generate_key(id, :time) = :key`)
			.pluck(true)
			.get({ key: msg.message.trim(), time })

		if (projectId) {
			await attachGroup(groupId, isChannel, projectId)
			await onGroupAttach(tgGroupId, isChannel)
		}
	}
		
	if (msg.message?.startsWith('/start')) {
		// Called by https://t.me/ready_or_not_2025_bot?startgroup=<customer_id/project_id>
		if (/start@ready_or_not_2025_bot (-|)([\d]+)$/g.test(msg.message)) {
			const tgUserId = msg.fromId?.userId?.value
			const param = +msg.message.split(' ')[1]

			// Set upload group for customer
			if (param < 0) {
				const customerId = -param

				db
					.prepare(`
						update customers 
						set upload_group_id = :group_id 
						where id = :customer_id and telegram_user_id = :telegram_user_id
					`)
					.safeIntegers(true)
					.run({
						group_id: groupId, 
						customer_id: customerId, 
						telegram_user_id: tgUserId
					})
			}

			// Add group to project
			if (param > 0) {
				const projectId = param
				
				const customerId = db
					.prepare(`select customer_id from projects where id = :project_id`)
					.pluck(true)
					.get({project_id: projectId})

				db
					.prepare(`
						update groups 
						set project_id = :project_id 
						where id = :group_id and exists(
							select 1 
							from customers 
							where id = :customer_id and telegram_user_id = :telegram_user_id)
					`)
					.safeIntegers(true)
					.run({
						project_id: projectId,
						group_id: groupId, 
						customer_id: customerId, 
						telegram_user_id: tgUserId
					})

				await reloadGroupUsers(groupId, false)
				await onGroupAttach(tgGroupId, isChannel)
			}
		}
	}
}

async function onNewUserMessage (msg) {
	if (msg.message == '/start' && msg.peerId?.className == 'PeerUser') {
		const tgUserId = msg.peerId?.userId?.value
		const userId = registerUser(tgUserId)

		try {
			const user = await client.getEntity(new Api.PeerUser({ userId: tgUserId }))
			updateUser(userId, user)
			await updateUserPhoto (userId, user)

			const appButton = new Api.KeyboardButtonWebView({
				text: "Open Mini-App", // Текст на кнопке
				url: "https://h5sj0gpz-3000.euw.devtunnels.ms/", // URL вашего Mini-App (HTTPS!)
			  });
			

			const inputPeer = new Api.InputPeerUser({userId: tgUserId, accessHash: user.accessHash.value})
			const resultBtn = await client.sendMessage(inputPeer, {
				message: 'Сообщение от бота',
				buttons: client.buildReplyMarkup([
					[Button.url('Админка', 'https://t.me/ready_or_not_2025_bot/userapp?startapp=admin')],
					[Button.url('Пользователь', 'https://t.me/ready_or_not_2025_bot/userapp?startapp=user')],
					[appButton]
				])
			})
		} catch (err) {
			console.error(msg.className + ', ' + userId + ': ' + err.message)
		}
	}
}

async function onUpdatePaticipant (update, isChannel) {
	const tgGroupId = isChannel ? update.channelId?.value : update.chatlId?.value
	if (!tgGroupId || update.userId?.value != bot.id)
		return

	const groupId = await registerGroup (tgGroupId, isChannel)

	const isBan = update.prevParticipant && !update.newParticipant
	const isAdd = (!update.prevParticipant || update.prevParticipant?.className == 'ChannelParticipantBanned') && update.newParticipant

	if (isBan || isAdd) 
		await reloadGroupUsers(groupId, isBan)

	if (isBan) { 
		db
			.prepare(`update groups set project_id = null where id = :group_id`)
			.run({group_id: groupId})
	}

	const botCanBan = update.newParticipant?.adminRights?.banUsers || 0
	db
		.prepare(`update groups set bot_can_ban = :bot_can_ban where id = :group_id`)
		.run({group_id: groupId, bot_can_ban: +botCanBan})
}

class Bot extends EventEmitter {
	
	async start (apiId, apiHash, botAuthToken) {
		this.id = 7236504417n

		client  = new TelegramClient(session, apiId, apiHash, {})

		client.addEventHandler(async (update) => {
			if (update.className == 'UpdateConnectionState') 
				return

			if (update.className == 'UpdateNewMessage' || update.className == 'UpdateNewChannelMessage') {
				const msg = update?.message
				const isChannel = update.className == 'UpdateNewChannelMessage'

				if (!msg) 
					return

				const result = msg.peerId?.className == 'PeerUser' ? await onNewUserMessage(msg) :
					msg.className == 'MessageService' ? await onNewServiceMessage(msg, isChannel) :
					await onNewMessage(msg, isChannel) 
			}

			if (update.className == 'UpdateChatParticipant' || update.className == 'UpdateChannelParticipant')
				await onUpdatePaticipant(update, update.className == 'UpdateChannelParticipant') 
		})
		
		await client.start({botAuthToken})
		console.log('SID: ', session.save())
	}

	async uploadDocument(projectId, fileName, mime, data, parentType, parentId, publishedBy) {	
		const file = await client.uploadFile({ file: new CustomFile(fileName, data.length, '', data), workers: 1 })
	
		const media = new Api.InputMediaUploadedDocument({
			file, 
			mimeType: mime,
			attributes: [new Api.DocumentAttributeFilename({ fileName })]
		})
	
		return await registerUpload({
			projectId, 
			media,
			parentType,
			parentId,
			publishedBy
		})	
	}
	
	async downloadDocument(projectId, documentId) {
		const document = db
			.prepare(`
				select file_id, access_hash, '' thumbSize, filename, mime
				from documents where id = :document_id and project_id = :project_id
			`)
			.safeIntegers(true)
			.get({project_id: projectId, document_id: documentId})
	
		if (!document)
			return false

		const result = await client.downloadFile(new Api.InputDocumentFileLocation({
			id: document.file_id,
			accessHash: document.access_hash,
			fileReference: Buffer.from(document.filename),
			thumbSize: ''
		}, {}))

		return {
			filename: document.filename,
			mime: document.mime,
			size: result.length,
			data: result
		}
	}
	
	async reloadGroupUsers(groupId, onlyReset) {
		return reloadGroupUsers(groupId, onlyReset)
	}

	async sendMessage (groupId, message) {
		const group = db
			.prepare(`select telegram_id, is_channel from groups where id = :group_id`)
			.get({ group_id: groupId})

		if (!group)
			return

		const entity =  group.is_channel ? { channelId: group.telegram_id } : { chatId: group.telegram_id }
		const inputPeer = await client.getEntity( group.is_channel ?
			new Api.InputPeerChannel(entity) :
			new Api.InputPeerChat(entity)
		)

		await client.sendMessage(inputPeer, {message})

		const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
		await delay(1000)
	}

	async leaveGroup (groupId) {
		const group = db
			.prepare(`select telegram_id, is_channel from groups where id = :group_id`)
			.get({ group_id: groupId})

		if (!group)
			return

		if (group.is_channel) {
			const inputPeer = await client.getEntity(new Api.InputPeerChannel({ channelId: group.telegram_id }))
			await client.invoke(new Api.channels.LeaveChannel({ channel: inputPeer }))
		} else {
			await client.invoke(new Api.messages.DeleteChatUser({ chatId: group.telegram_id, userId: this.id }))
		}
	}
} 

const bot = new Bot()

module.exports = bot

