import { PeerInfo } from '../types/basicTypes'
import { files, paths, readAndValidateFile } from './files'
import logger from './logger'
import { PostResult, parseCookie, post } from './networkUtils'
import utils from './utils'

export function peerToString(peer: {
    host: string
    port: string | number
}): string {
    return `${peer.host}:${peer.port}`
}

export function isPeerSameAs(
    peer1: { host: string; port: number },
    peer2: { host: string; port: number }
): boolean {
    return peer1.host === peer2.host && peer1.port === peer2.port
}

export function updatePeersFile(
    peerToUpdate: PeerInfo,
    updatedPeer: Partial<PeerInfo>
): void {
    const newVal = readAndValidateFile<PeerInfo[]>(files.peersFile)
    let peers
    if (newVal) {
        peers = newVal
    }
    if (!peers)
        throw new Error('Peers file was invalid while trying to update it!')

    const updatedPeers = peers.map((x) => {
        if (isPeerSameAs(peerToUpdate, x)) {
            return {
                ...x,
                ...updatedPeer,
            }
        } else {
            return x
        }
    })

    utils.WriteFile(JSON.stringify(updatedPeers, null, 2), paths.peersFile)
}

export async function loginToPeer(peer: PeerInfo): Promise<string | Error> {
    const { data, error, cookie } = await post<{
        result: string
        msg: string
    }>({
        hostname: peer.host,
        path: '/api/login',
        port: peer.port,
        bodyObject: { pw: peer.pw },
        http: peer.http,
    })

    if (error || !data || data.result !== 'success') {
        return data ? new Error(data.msg) : error
    }

    const parsedCookies = parseCookie(cookie)
    return parsedCookies.sessionID
}

export async function loginAndPostDataToAllPeers<
    T extends { result?: string; success?: boolean }
>(
    peers: PeerInfo[],
    postDataFn: (
        peer: PeerInfo,
        sessionCookie: string
    ) => Promise<PostResult<T>>,
    resultCallback?: (peer: PeerInfo, res: PostResult<T>) => void
): Promise<void> {
    const results: {
        errors: PeerInfo[]
        sent: PeerInfo[]
        loginErrors: PeerInfo[]
    } = {
        errors: [],
        sent: [],
        loginErrors: [],
    }

    for (const peer of peers) {
        try {
            let sessionCookie = peer.sessionCookie

            const login = async (peer: PeerInfo) => {
                const loginResult = await loginToPeer(peer)
                if (typeof loginResult === 'string') {
                    sessionCookie = loginResult
                    updatePeersFile(peer, { sessionCookie: loginResult })
                } else {
                    throw new Error('Error logging in to' + peerToString(peer))
                }
            }

            if (!sessionCookie) {
                await login(peer)
            }

            let res = await postDataFn(peer, sessionCookie)

            if (res.data?.result === 'nouser' && sessionCookie) {
                await login(peer)

                res = await postDataFn(peer, sessionCookie)
            }

            if (res.error || !res.data?.success) {
                results.errors.push(peer)
                console.error(res.error || JSON.stringify(res.data))
            } else {
                results.sent.push(peer)
            }

            if (resultCallback) resultCallback(peer, res)
        } catch (e) {
            results.loginErrors.push(peer)
        }
    }

    const logMsg: string[] = []
    const addToLogMsg = (
        peerResult: PeerInfo[],
        prefix: string,
        color: string
    ) => {
        if (peerResult.length > 0) {
            logMsg.push(
                `${logger.C(color)}${prefix}:${logger.C()} ` +
                    peerResult.map((x) => peerToString(x)).join(', ')
            )
        }
    }
    addToLogMsg(results.loginErrors, 'Login error', 'red')
    addToLogMsg(results.errors, 'Error', 'red')
    addToLogMsg(results.sent, 'Sent', 'green')

    logger.Log(
        `\t${logger.C('green')}Sent data to peers${logger.C()}; ${logMsg.join(
            ', '
        )}`
    )
}
