import { Database } from 'better-sqlite3'
import dbtools from '../../../utils/dbtools'
import { PeerInfo, User } from '../../../types/basicTypes'
import { decrypt, encrypt } from '../../../utils/encryption'
import {
    SyncDataResult,
    SyncResponseBase,
    SyncResult,
    peerToString,
    updatePeersFile,
} from './p2putils'
import logger from '../../../utils/logger'

interface SyncUsersProps {
    userData: (SyncDataResult['users'] & { peer: PeerInfo })[]
    syncStart: number
    userDB: Database
    privateKey: string
}

interface HandleNewUsersProps {
    encryptedNewUsers: string
    host: string
    peers: PeerInfo[]
    privateKey: string
    userDB: Database
}

// ---------------------------------------------------------------------------------------------
// Getting
// ---------------------------------------------------------------------------------------------

export function getNewUsersSince(since: number, userDB: Database): User[] {
    const users: User[] = dbtools.runStatement(
        userDB,
        `SELECT *
            FROM users
            WHERE created >= ${since}
            AND id != 1
            AND isAdmin is null
            ;`
    )
    return users
}

export function getUsers(
    remoteHost: string,
    remotePeerInfo: PeerInfo,
    usersSince: number,
    userDB: Database
): SyncResponseBase & {
    encryptedUsers?: string
    sentUsers?: number
} {
    let sentUsers = 0
    if (!remoteHost) {
        return {
            success: false,
            message:
                'remoteHost key is missing from body. Users will not be sent',
        }
    }
    if (!remotePeerInfo) {
        return {
            success: false,
            message: `couldn't find remote peer info based on remoteHost (${remoteHost}). Users will not be sent`,
        }
    }

    const remotePublicKey = remotePeerInfo.publicKey
    if (remotePublicKey) {
        // FIXME: sign data?
        const newUsers = getNewUsersSince(usersSince, userDB)
        sentUsers = newUsers.length
        const encryptedUsers = encrypt(
            remotePublicKey,
            JSON.stringify(newUsers)
        )

        return {
            success: true,
            encryptedUsers: encryptedUsers,
            sentUsers: sentUsers,
        }
    } else if (remotePeerInfo) {
        return {
            success: false,
            message: `Warning: "${peerToString(
                remotePeerInfo
            )}" has no public key saved! Users will not be sent`,
        }
    }
    return { success: false, message: 'this shouldt be a case lol' }
}

// ---------------------------------------------------------------------------------------------
// Adding new
// ---------------------------------------------------------------------------------------------

export function handleNewUsers(props: HandleNewUsersProps): {
    success: boolean
    addedUserCount?: number
    message?: string
} {
    const result = addNewUsers(props)

    if (!result.success) {
        logger.Log(
            `Error while adding new users file: "${result.message}", from host: "${props.host}"`,
            'yellowbg'
        )
    }

    return result
}

function addNewUsers({
    encryptedNewUsers,
    host,
    peers,
    privateKey,
    userDB,
}: HandleNewUsersProps): {
    success: boolean
    addedUserCount?: number
    message?: string
} {
    if (!encryptedNewUsers || !host) {
        return {
            success: false,
            message: 'encryptedNewUsers or host key are missing from body',
        }
    }

    const remotePeerInfo = peers.find((peer) => {
        return peerToString(peer) === host
    })

    if (!remotePeerInfo) {
        return {
            success: false,
            message: "couldn't find remote peer info based on host",
        }
    }

    const decryptedUsers: User[] = JSON.parse(
        decrypt(privateKey, encryptedNewUsers)
    )

    const addedUserCount = addUsersToDb(decryptedUsers, userDB, {
        sourceHost: peerToString(remotePeerInfo),
    })

    if (addedUserCount > 0) {
        logger.Log(
            `\tAdded ${addedUserCount} new users from "${peerToString(
                remotePeerInfo
            )}"`,
            'cyan'
        )
    }

    return { success: true, addedUserCount: addedUserCount }
}

// ---------------------------------------------------------------------------------------------
// Syncing utils
// ---------------------------------------------------------------------------------------------

function addUsersToDb(
    users: User[],
    userDB: Database,
    extraProps: Partial<User>
) {
    let addedUserCount = 0
    users.forEach((remoteUser) => {
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        const { id, ...remoteUserWithoutId } = remoteUser
        const localUser = dbtools.Select(userDB, 'users', {
            pw: remoteUser.pw,
        })
        if (localUser.length === 0) {
            addedUserCount += 1
            // FIXME: users will not have consistend id across servers. This may be
            // harmless, will see
            dbtools.Insert(userDB, 'users', {
                ...(remoteUserWithoutId as Omit<User, 'id'>),
                ...extraProps,
            })
        }
    })
    return addedUserCount
}

// ---------------------------------------------------------------------------------------------
// Syncing
// ---------------------------------------------------------------------------------------------

export async function syncUsers({
    userData,
    syncStart,
    userDB,
    privateKey,
}: SyncUsersProps): Promise<SyncResult> {
    logger.Log('Syncing users...')
    let totalRecievedUsers = 0
    const resultsCount: {
        [key: string]: {
            newUsers?: number
        }
    } = {}
    const oldUserCount = dbtools.SelectAll(userDB, 'users').length

    try {
        userData.forEach((res) => {
            if (res.encryptedUsers) {
                const decryptedUsers: User[] = JSON.parse(
                    decrypt(privateKey, res.encryptedUsers)
                )
                const addedUserCount = addUsersToDb(decryptedUsers, userDB, {
                    sourceHost: peerToString(res.peer),
                })
                resultsCount[peerToString(res.peer)] = {
                    newUsers: addedUserCount,
                }
                totalRecievedUsers += decryptedUsers.length
                updatePeersFile(res.peer, {
                    lastUsersSync: syncStart,
                })
            }
        })
    } catch (e) {
        logger.Log('\tError while trying to sync users: ' + e.message, 'redbg')
        console.error(e)
    }
    const newUserCount = dbtools.SelectAll(userDB, 'users').length

    if (totalRecievedUsers === 0) {
        logger.Log(
            `No peers returned any new users. User sync successfully finished!`,
            'green'
        )
    } else {
        logger.logTable(
            [
                ['', 'Users'],
                ['Old', oldUserCount],
                ...Object.entries(resultsCount).map(([key, result]) => {
                    return [key, result.newUsers]
                }),
                ['Added total', newUserCount - oldUserCount],
                ['Final', newUserCount],
            ],
            { colWidth: [20], rowPrefix: '\t' }
        )
        logger.Log(`Successfully synced users!`, 'green')
    }

    return {
        old: {
            users: oldUserCount,
        },
        added: {
            users: newUserCount - oldUserCount,
        },
        final: {
            users: newUserCount,
        },
    }
}
