import 'websocket-polyfill'
import { SSMClient, GetParameterCommand, PutParameterCommand } from '@aws-sdk/client-ssm'
import { extract } from '@extractus/feed-extractor'
import NDK, { NDKEvent, NDKPrivateKeySigner } from '@nostr-dev-kit/ndk'
import bech32 from 'bech32-buffer'
import moment from 'moment'
import FormData from 'form-data'

const toHexString = nsec => {
    let buffer = bech32.decode(nsec).data
    return buffer.reduce((s, byte) => {
        let hex = byte.toString(16);
        if (hex.length === 1) hex = '0' + hex;
        return s + hex;
    }, '');
}

const imgUrls = html => {
    const re = /<img[^>]+src="?([^"\s]+)"?[^>]*\/>/g
    const urls = []
    let m

    while (m = re.exec(html)) {
        urls.push(m[1])
    }

    return urls
}

const putImgPlaceholders = html => {
    const re = /<img[^>]+src="?([^"\s]+)"?[^>]*\/>/g
    const placeholders = []
    let replaced = html
    const matches = html.matchAll(re)
    let img = 0

    for(let match of matches) {
        replaced = replaced.replace(match[0], `%%${img}%%`)
        placeholders.push(`%%${img}%%`)
        img++
    }

    return {
        replaced,
        placeholders
    }
}

const stripHtml = html => {
    return html.replaceAll(/<[^>]*>/g, "")
}

const replaceImgsWithNostrBuildUrls = async (storeUrl, html, npub) => {
    const urls = imgUrls(html)
    let { replaced, placeholders } = putImgPlaceholders(html)

    let count = 0
    for (let placeholder of placeholders) {
        const nbUrl = await uploadImgStore(storeUrl, urls[count], npub)
        if (nbUrl) {
            replaced = replaced.replace(placeholder, nbUrl)
        } else {
            replaced = replaced.replace(placeholder, '')
        }
        count++
    }
    return replaced
}

const uploadImgStore = async (hostUrl, imgUrl, npub) => {
    const response = await fetch(hostUrl, {
        method: 'POST',
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: new URLSearchParams({
            "url": imgUrl,
            "npub": npub
        }) })
    const json = await response.json()

    if (json.status !== "success") {
        return false
    }
    return json.data[0].url
}
    
export const handler = async (event) => {
    const region = process.env.AWS_REGION || "us-east-1"
    const feed = event.feedUrl
    const nostrNsecParam = event.nostrNsecParam
    const lastRunTimeParam = event.lastRunTimeParam
    const dryrun = event.dryRun

    if (dryrun) {
        console.log("Dry run. Simulating publishes to nostr.")
    }

    let response = {
        totalItems: 0,
        successes: 0,
        errors: []
    }

    const ssmClient = new SSMClient({ region })
    const ssmGetLastRunCommand = new GetParameterCommand({
        Name: lastRunTimeParam
    });

    let since = new Date()
    await ssmClient.send(ssmGetLastRunCommand)
        .then(data => {
            since = data.Parameter.Value
        }).catch(err => {
            console.dir(err)
            response.errors.push(err)
            return response
        })

    const ssmGetNsecCommand = new GetParameterCommand({
        Name: nostrNsecParam,
        WithDecryption: true
    })

    let privkey = ''
    await ssmClient.send(ssmGetNsecCommand)
        .then(data => {
            privkey = toHexString(data.Parameter.Value)
        }).catch(err => {
            console.dir(err)
            response.errors.push(err)
            return response
        })

    if (feed === "") {
        console.dir("feedUrl was not set.")
        return {
            statusCode: 400,
            body: "feedUrl must be provided in payload."
        }
    }

    const ndk = new NDK({
        explicitRelayUrls: [
            "wss://nostr.mom",
            "wss://nostr.snort.social",
            "wss://nostr.lol",
            "wss://nostr.oxtr.dev",
            "wss://relay.nostr.bg",
            "wss://nostr-pub.wellorder.net",
            "wss://relay.damus.io",
            "wss://nostr.milou.lol",
            "wss://nostr.bitcoiner.social"
        ]
    })

    await ndk.connect(6000).catch(err => {
        console.dir(err)
        response.errors.push("failed to connect to relay", err)
    })

    const signer = new NDKPrivateKeySigner(privkey)
    ndk.signer = signer

    let rss = await extract(feed, { descriptionMaxLen: 0, normalization: false })
        .catch(err => {
            console.dir(err);
            return response
        })

    let filtered = rss.item.filter(item => moment(item.pubDate).isAfter(since))
    response.totalItems = filtered.length

    for (let item of filtered) {
        const user = await ndk.signer.user()
        let content = ''
        let replaced = await replaceImgsWithNostrBuildUrls(
            'https://nostr.build/api/v2/upload/url',
            item.description,
            user.npub)
        if (replaced) {
            content = stripHtml(replaced)
        }

        let ndkEvent = new NDKEvent(ndk, {
            kind: 1,
            pubkey: user.hexpubkey,
            content,
            created_at: Math.floor(Date.now() / 1000),
        })

        if (dryrun) {
            console.dir(content)
            response.successes++
            continue
        }

        try {
            await ndkEvent.sign(ndk.signer)
            const published = await ndkEvent.publish()
            if (published) {
                response.successes++
                const ssmPutCommand = new PutParameterCommand({
                    Name: lastRunTimeParam,
                    Value: new Date().toISOString(),
                    Type: "String",
                    Overwrite: true
                })
                await ssmClient.send(ssmPutCommand)
                continue
            }
            console.dir(`failed to publish note`)
            response.errors.push(`failed to publish note`)
        } catch(e) {
            console.dir(e)
            response.errors.push(e)
            return response
        }
    }

    return response
}

