const fs = require('fs')
const os = require('os')
const util = require('util')
const axios = require('axios')
const bytes = require('bytes')
const sharp = require('sharp')
const morgan = require('morgan')
const express = require('express')
const cp = require('child_process')
const PDFDocument = require('pdfkit')
const playwright = require('playwright-extra')
// const stealth = require('puppeteer-extra-plugin-stealth')
// playwright.chromium.use(stealth())
const { NinexbuddySource, NinexbuddyScraper } = require("./lib/buddy.js")
const kyou = new NinexbuddyScraper()


const app = express()
app.set('json spaces', 4)
app.use(morgan('dev'))

const limitSize = '500mb'
app.use(express.json({ limit: limitSize }))
app.use(express.urlencoded({ extended: true, limit: limitSize }))

const tmpFolder = os.tmpdir()
app.use((req, res, next) => {
	fs.readdirSync(tmpFolder).map(file => {
		file = `${tmpFolder}/${file}`
		let stats = fs.statSync(file)
		if (!stats.isFile()) return
		if (Date.now() - stats.mtimeMs >= 1000 * 60 * 30) {
			fs.unlinkSync(file)
			console.log('Deleted file', file)
		}
	})
	next()
})

app.use('/file', express.static(tmpFolder))

app.all('/', (req, res) => {
	const status = {}
	const used = process.memoryUsage()
	for (let key in used) status[key] = formatSize(used[key])
	
	const disk = cp.execSync('du -sh').toString().split('M')[0]
	status.diskUsage = `${disk} MB`
	
	const totalmem = os.totalmem()
	const freemem = os.freemem()
	status.memoryUsage = `${formatSize(totalmem - freemem)} / ${formatSize(totalmem)}`
	
	res.json({
		creator: '@rippanteq7',
		message: 'Hello World',
		uptime: new Date(process.uptime() * 1000).toUTCString().split(' ')[4],
		status
	})
})

app.all('/imagetopdf', async (req, res) => {
	if (!['POST'].includes(req.method)) return res.status(405).json({ success: false, message: 'Method Not Allowed' })
	
	try {
		console.log(new Date().toLocaleString('id', { timeZone: 'Asia/Jakarta' }), '\n', req.body)
		const { images } = req.body
		if (!(images && Array.isArray(images))) return res.json({ success: false, message: 'Required an array image url' })
		
		const buffer = await toPDF(images)
		res.setHeader('Content-Disposition', `attachment; filename=${Math.random().toString(36).slice(2)}.pdf`)
		res.setHeader('Content-Type', 'application/pdf')
		res.setHeader('Content-Length', buffer.byteLength)
		res.send(buffer)
	} catch (e) {
		console.log(e)
		e = String(e)
		res.status(500).json({ error: true, message: e === '[object Object]' ? 'Internal Server Error' : e })
	}
})

app.all('/webp2png', async (req, res) => {
	if (!['POST'].includes(req.method)) return res.status(405).json({ success: false, message: 'Method Not Allowed' })
	
	try {
		const { file } = req.body
		if (!(file && isBase64(file))) return res.json({ success: false, message: 'Payload body file must be filled in base64 format' })
		
		const fileBuffer = Buffer.from(file, 'base64')
		const fileName = `${Math.random().toString(36).slice(2)}.png`
		const convertData = await sharp(fileBuffer).png().toBuffer()
		
		await fs.promises.writeFile(`${tmpFolder}/${fileName}`, convertData)
		res.send(`https://${req.get('host')}/file/${fileName}`)
	} catch (e) {
		console.log(e)
		e = String(e)
		res.status(500).json({ error: true, message: e === '[object Object]' ? 'Internal Server Error' : e })
	}
})

app.all(['/webp2gif', '/webp2mp4'], async (req, res) => {
	if (!['POST'].includes(req.method)) return res.status(405).json({ success: false, message: 'Method Not Allowed' })
	
	try {
		const { file } = req.body
		if (!(file && isBase64(file))) return res.json({ success: false, message: 'Payload body file must be filled in base64 format' })
		
		const fileBuffer = Buffer.from(file, 'base64')
		const fileName = `${Math.random().toString(36).slice(2)}.webp`
		const filePath = `${tmpFolder}/${fileName}`
		await fs.promises.writeFile(filePath, fileBuffer)
		
		const exec = util.promisify(cp.exec).bind(cp)
		await exec(`convert ${filePath} ${filePath.replace('.webp', '.gif')}`)
		if (/gif/.test(req.path)) return res.send(`https://${req.get('host')}/file/${fileName.replace('.webp', '.gif')}`)
		
		await exec(`ffmpeg -i ${filePath.replace('.webp', '.gif')} -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" ${filePath.replace(/.webp|.gif/g, '')}.mp4`)
		res.send(`https://${req.get('host')}/file/${fileName.replace('.webp', '.mp4')}`)
	} catch (e) {
		console.log(e)
		e = String(e)
		res.status(500).json({ error: true, message: e === '[object Object]' ? 'Internal Server Error' : e })
	}
})

app.get('/fetch', async (req, res) => {
	try {
		if (!req.query.url) return res.json({ message: 'Required an url' })
		let json = await axios.get(req.query.url)
		res.json(json.data)
	} catch (e) {
		res.send(e)
	}
})

app.all(['/enhance', '/hd', '/upscale'], async (req, res) => {
	if (!['GET', 'POST'].includes(req.method)) return res.status(405).json({ success: false, message: 'Method Not Allowed' })
	
	try {
		const { url } = req.method !== 'GET' ? req.body : req.query
		if (!url) return res.json({ success: false, message: 'Required parameter url' })
		
		const result = await enhanceImage(url)
		res.json({ success: true, result })
	} catch (e) {
		console.log(e)
		e = String(e)
		res.status(500).json({ error: true, message: e === '[object Object]' ? 'Internal Server Error' : e })
	}
})

app.all('/animelast', async (req, res) => {
        const result = await scrapeAnimeInfo();       
        res.json({
            creator: '@Kyouka',
            message: 'Success',
            uptime: new Date(process.uptime() * 1000).toUTCString().split(' ')[4],
            result
        });
});

app.all('/dood', async (req, res) => {
        const { url } = req.query
		if (!url) return res.json({ success: false, message: 'Required parameter url' })
        const result = await kyou.execWithArgs(url)  
        res.json({
            creator: '@Kyouka',
            message: 'Success',
            uptime: new Date(process.uptime() * 1000).toUTCString().split(' ')[4],
            result
        });
});

app.get('/igstalk', async (req, res) => {
	try {
		if (!req.query.user) return res.json({ message: 'Required an username' })
		let result = await igStalk(req.query.user)
		res.json({ result })
	} catch (e) {
		res.send(e)
	}
})

app.all('/stablediff/illusion', async (req, res) => {
	if (!['GET', 'POST'].includes(req.method)) return res.status(405).json({ success: false, message: 'Method Not Allowed' })
	
	try {
		const {
			prompt,
			negative_prompt = 'low quality',
			image_url,
			num_inference_steps = 25,
			controlnet_conditioning_scale = 1
		} = req.method !== 'GET' ? req.body : req.query
		
		if (!(prompt && image_url)) return res.json({ success: false, message: 'Required parameter prompt & image_url' })
		
		const headers = {
			'Content-Type': 'application/json',
			Authorization: `Key ${process.env.falAIKey}`
		}
		
		const { detail, response_url } = await (await fetch('https://54285744-illusion-diffusion.gateway.alpha.fal.ai/fal/queue/submit', {
			method: 'POST',
			body: JSON.stringify({
				prompt, negative_prompt, image_url,
				num_inference_steps, controlnet_conditioning_scale
			}),
			headers
		})).json()
		if (detail) return res.json({ success: false, message: detail })
		
		let retry = 0
		while (true) {
			await new Promise(resolve => setTimeout(resolve, 2500))
			const prediction = await (await fetch(response_url, { headers })).json()
			console.log(prediction)
			if (retry > 10) return res.json({ success: false, message: prediction.detail || 'Max retry has reached' })
			if (prediction.image) return res.json({ success: true, result: prediction })
			retry += 1
		}
		
	} catch (e) {
		console.log(e)
		e = String(e)
		res.status(500).json({ error: true, message: e === '[object Object]' ? 'Internal Server Error' : e })
	}
})

app.get('/dongo', async (req, res) => {
  const { url } = req.query
		if (!url) return res.json({ success: false, message: 'Required parameter url' })
  const browser = await playwright.chromium.launch({
		headless: true,
		executablePath: '/usr/bin/chromium',
		args: ['--no-sandbox']
	})
  const context = await browser.newContext({
    extraHTTPHeaders: {
      'accept': '*/*',
      'accept-language': 'en-US,en;q=0.9,id;q=0.8',
      'cache-control': 'no-cache',
      'origin': url,
      'pragma': 'no-cache',
      'referer': url,
      'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
      'sec-ch-ua-mobile': '?0',
      'sec-ch-ua-platform': '"Windows"',
      'sec-fetch-dest': 'script',
      'sec-fetch-mode': 'cors',
      'sec-fetch-site': 'cross-site',
      'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
    }
  });

  const page = await context.newPage();
  await page.goto(url);

  const content = await page.content(); // Get the page content as HTML

  await browser.close();

  res.send(content); // Send the page content as response
});

app.get('/fetch-page', async (req, res) => {
  const browser = await playwright.chromium.launch({
		headless: true,
		executablePath: '/usr/bin/chromium',
		args: ['--no-sandbox']
	})
  const context = await browser.newContext({
    extraHTTPHeaders: {
      'accept': '*/*',
      'accept-language': 'en-US,en;q=0.9,id;q=0.8',
      'cache-control': 'no-cache',
      'origin': 'https://doujindesu.tv',
      'pragma': 'no-cache',
      'referer': 'https://doujindesu.tv',
      'sec-ch-ua': '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
      'sec-ch-ua-mobile': '?0',
      'sec-ch-ua-platform': '"Windows"',
      'sec-fetch-dest': 'script',
      'sec-fetch-mode': 'cors',
      'sec-fetch-site': 'cross-site',
      'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36'
    }
  });

  const page = await context.newPage();
  await page.goto('https://doujindesu.tv');

  const content = await page.content(); // Get the page content as HTML

  await browser.close();

  res.send(content); // Send the page content as response
});

const PORT = process.env.PORT || 7860
app.listen(PORT, () => console.log('App running on port', PORT))

function formatSize(num) {
	return bytes(+num || 0, { unitSeparator: ' ' })
}

function isBase64(str) {
	try {
		return btoa(atob(str)) === str
	} catch {
		return false
	}
}

function toPDF(urls) {
	return new Promise(async (resolve, reject) => {
		try {
			if (!Array.isArray(urls)) urls = [urls]
			const doc = new PDFDocument({ margin: 0, size: 'A4' })
			const buffers = []
			
			for (let i = 0; i < urls.length; i++) {
				const response = await fetch(urls[i], { headers: { referer: urls[i] }})
				if (!response.ok) continue
				
				const type = response.headers.get('content-type')
				if (!/image/.test(type)) continue
				
				let buffer = Buffer.from(await response.arrayBuffer())
				if (/gif|webp/.test(type)) buffer = await sharp(buffer).png().toBuffer()
				
				doc.image(buffer, 0, 0, { fit: [595.28, 841.89], align: 'center', valign: 'center' })
				if (urls.length !== i + 1) doc.addPage()
			}
			
			doc.on('data', (chunk) => buffers.push(chunk))
			doc.on('end', () => resolve(Buffer.concat(buffers)))
			doc.on('error', reject)
			doc.end()
		} catch (e) {
			console.log(e)
			reject(e)
		}
	})
}

async function scrapeAnimeInfo() {
  try {
    const browser = await playwright.chromium.launch({
      headless: true,
      executablePath: '/usr/bin/chromium',
      args: ['--no-sandbox']
    });
    const page = await browser.newPage();
    await page.goto('http://66.29.129.161/?filter=latest&cat=1');
    const pageContent = await page.content();
    console.log(pageContent);
    // Wait for the list of videos to load
    await page.waitForSelector('.videos-list');

    // Extract data from all articles
    const videoData = await page.$$eval('.videos-list article', articles => {
      return articles.map(article => {
        const thumbnail = article.querySelector('.post-thumbnail img').src;
        const title = article.querySelector('header.entry-header span').textContent;
        const url = article.querySelector('a').href;
        const duration = article.querySelector('.duration').textContent.trim();
        return { thumbnail, title, url, duration };
      });
    });

    await browser.close();
    return videoData;
  } catch (error) {
    console.error('An error occurred during scraping:', error);
    return []; // Return an empty array in case of error
  }
}


async function enhanceImage(url) {
	const browser = await playwright.chromium.launch({
		headless: true,
		executablePath: '/usr/bin/chromium',
		args: ['--no-sandbox']
	})
	
	const page = await browser.newPage()
	await page.goto('https://snapedit.app/id/enhance/upload')
	await page.waitForLoadState('domcontentloaded')
	
	const arrayBuffer = await (await fetch(url)).arrayBuffer()
	page.on('filechooser', (fileChooser) => {
		const fileObject = {
			name: 'file.jpg',
			mimeType: 'image/jpg',
			buffer: Buffer.from(arrayBuffer)
		}
		fileChooser.setFiles([fileObject])
	})
	
	await page.getByRole('button', { name: 'Unggah gambar', exact: true }).click()
	const response = await page.waitForResponse(res => {
		console.log(res.url())
		return res.url().includes('api/enhance/v1')
	}, { timeout: 60 * 1000 })
	const json = await response.json()
	
	await browser.close()
	return json
}


async function igStalk(user) {
	const getDetailPost = async url => {
		let html = (await axios.get(url, { headers: { 'Referer': 'https://www.picuki.com/', 'User-Agent': fakeua.mobile() }})).data 
		let $ = cheerio.load(html), obj = {}
		obj.caption = $('title').text().trim().split(' Instagram post ')[1].split(' - Picuki.com')[0]
		obj.ago = $('div.single-photo-info').find('div.single-photo-time').text()
		obj.likes = $('div.info-bottom').find('span.icon-thumbs-up-alt').text()
		obj.comments = $('div.info-bottom').find('span.icon-chat').text()
		obj.url = $('div.single-photo.owl-carousel.owl-theme > div.item').get().map((x) => $(x).find('img').attr('src') || $(x).find('video').attr('src'))
		if (!obj.url.length) obj.url = [$('div.single-photo').find('img').attr('src') || $('div.single-photo').find('video').attr('src')]
		return obj
	}
	let html = (await axios.get('https://www.picuki.com/profile/' + user, { headers: { 'Referer': 'https://www.picuki.com/', 'User-Agent': fakeua.mobile() }})).data
	let $ = cheerio.load(html), obj = {}, arr = []
	let urlPost = $('div.content > ul > li').get().map((x) => $(x).find('a').attr('href'))
	for (let x of urlPost) {
		if (x) arr.push(await getDetailPost(x))
	}
	obj.avatar = $('div.profile-avatar').find('a').attr('href')
	obj.username = $('div.profile-name > h1').text()
	obj.fullname = $('div.profile-name > h2').text()
	obj.description = $('div.profile-description').text().trim()
	obj.followers = $('div.content-title').find('span.followed_by').text()
	obj.following = $('div.content-title').find('span.follows').text()
	obj.post = arr
	return obj
}