/*
 *     The Peacock Project - a HITMAN server replacement.
 *     Copyright (C) 2021-2022 The Peacock Project Team
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Affero General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Affero General Public License for more details.
 *
 *     You should have received a copy of the GNU Affero General Public License
 *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

import { log, LogLevel } from "../loggingInterop"
import { Router } from "express"
import { enqueuePushMessage } from "../eventHandler"
import { json as jsonMiddleware } from "body-parser"
import {
    ClientToServerEvent,
    ContractSession,
    RequestWithJwt,
    UserCentricContract,
} from "../types/types"
import { nilUuid } from "../utils"
import { randomUUID } from "crypto"
import { getConfig } from "../configSwizzleManager"
import { generateUserCentric } from "../contracts/dataGen"
import { controller } from "../controller"
import { MatchOverC2SEvent } from "../types/events"

/**
 * A multiplayer preset.
 */
export interface MultiplayerPreset {
    /**
     * The preset's ID.
     */
    Id: string
    /**
     * The preset's game mode.
     */
    GameMode: "versus" | string
    Metadata: {
        Title: string
        Header: string
        Image: string
        IsDefault: boolean
    }
    Data: {
        Contracts: string[]
        Properties: {
            mode: string
            active: boolean
            __comment?: string
        }
    }
}

export interface MatchData {
    Players: string[]
    MatchData: {
        contractId: string
        [key: string]: unknown
    }
}

/**
 * Extension for a session providing ghost mode details.
 */
export interface SessionGhostModeDetails {
    deaths: number
    unnoticedKills: number
    Opponents: string[]
    IsWinner: boolean
    Score: number
    OpponentScore: number
    IsDraw: boolean
    timerEnd: number | null
}

export const multiplayerRouter = Router()
const activeMatches: Map<string, MatchData> = new Map()

multiplayerRouter.post(
    "/GetRequiredResourcesForPreset",
    jsonMiddleware(),
    (req: RequestWithJwt, res) => {
        const allPresets = getConfig<MultiplayerPreset[]>(
            "MultiplayerPresets",
            false,
        )

        const requestedPreset = allPresets.find(
            (preset) => preset.Id === req.body.id,
        )

        if (!requestedPreset) {
            res.status(404).end()
            log(LogLevel.WARN, "unknown multiplayer preset id requested")
            return
        }

        const contractIds = requestedPreset.Data.Contracts
        const userCentrics = contractIds
            .map((id) =>
                generateUserCentric(
                    controller.resolveContract(id),
                    req.jwt.unique_name,
                    req.gameVersion,
                ),
            )
            .filter(Boolean)

        res.json(
            userCentrics.map((userCentric: UserCentricContract) => ({
                Id: userCentric.Contract.Metadata.Id,
                DlcId: userCentric.Data.DlcName,
                Resources: [
                    userCentric.Contract.Metadata.ScenePath,
                    ...(userCentric.Contract.Data.Bricks ?? []),
                ],
            })),
        )
    },
)

multiplayerRouter.post(
    "/RegisterToMatch",
    jsonMiddleware(),
    (req: RequestWithJwt, res) => {
        // get a random contract from the list of possible ones in the selected preset
        const multiplayerPresets = getConfig<MultiplayerPreset[]>(
            "MultiplayerPresets",
            false,
        )

        if (!req.body.presetId) {
            req.body.presetId = "d72d7cc9-ee26-4c7d-857a-75abdc9ccb61" // default to miami invite preset
        }

        const preset = multiplayerPresets.find(
            (preset) => preset.Id === req.body.presetId,
        )

        if (!preset) {
            res.status(404).end()
            log(
                LogLevel.WARN,
                `Unknown preset id requested (${req.body.presetId})`,
            )
            return
        }

        const contractId =
            preset.Data.Contracts[
                Math.trunc(Math.random() * preset.Data.Contracts.length)
            ]

        if (req.body.matchId === nilUuid) {
            // create new match
            req.body.matchId = randomUUID()
            activeMatches.set(req.body.matchId, {
                MatchData: {
                    contractId: contractId,
                },
                Players: [req.jwt.unique_name],
            })
        } else if (activeMatches.has(req.body.matchId)) {
            // join existing match
            const match = activeMatches.get(req.body.matchId)!

            match.Players.forEach((playerId) =>
                enqueuePushMessage(playerId, {
                    MatchId: req.body.matchId,
                    Type: 1,
                    PlayerId: req.jwt.unique_name,
                    MatchData: null,
                }),
            )

            match.Players.push(req.jwt.unique_name)
        } else {
            // MatchId not found
            res.status(404).end()
            return
        }

        enqueuePushMessage(req.jwt.unique_name, {
            MatchId: req.body.matchId,
            Type: 3,
            PlayerId: nilUuid,
            MatchData: activeMatches.get(req.body.matchId)!.MatchData,
        })

        res.json({
            MatchId: req.body.matchId,
            PreferedHostIndex: 0,
            Tickets: [],
            MatchMode: null,
            MatchData: null,
            MatchStats: {},
            MatchType: 0,
        })
    },
)

multiplayerRouter.post(
    "/SetMatchData",
    jsonMiddleware(),
    (req: RequestWithJwt, res) => {
        const match = activeMatches.get(req.body.matchId)

        if (!(match && match.Players.includes(req.jwt.unique_name))) {
            res.status(404).end()
            return
        }

        match.MatchData[req.body.key] = req.body.value
        res.json({
            MatchId: req.body.matchId,
            PreferedHostIndex: 0,
            Tickets: [],
            MatchMode: null,
            MatchData: match.MatchData,
            MatchStats: {},
            MatchType: 0,
        })
    },
)

multiplayerRouter.post("/RegisterToPreset", jsonMiddleware(), (req, res) => {
    // matchmaking
    // TODO: implement matchmaking
    // req.body.presetId
    // req.body.lobbyId (this is just a timestamp?)
    res.status(500).end()
})

export function handleMultiplayerEvent(
    event: ClientToServerEvent,
    session: ContractSession,
): boolean {
    const emptySession = <SessionGhostModeDetails>{}
    const ghost = session.ghost || emptySession

    switch (event.Name) {
        case "Ghost_PlayerDied":
            ghost.deaths += 1
            return true
        case "Ghost_TargetUnnoticed":
            ghost.unnoticedKills += 1
            return true
        case "Opponents": {
            const value = event.Value as {
                ConnectedSessions: string[]
            }

            ghost.Opponents = value.ConnectedSessions
            return true
        }
        case "MatchOver": {
            const matchOverValue = (event as MatchOverC2SEvent).Value

            ghost.Score = matchOverValue.MyScore
            ghost.OpponentScore = matchOverValue.OpponentScore
            ghost.IsWinner = matchOverValue.IsWinner
            ghost.IsDraw = matchOverValue.IsDraw
            ghost.timerEnd = event.Timestamp

            return true
        }
        default:
            return false
    }
}