import { APIRoute } from "astro";
import validateAccessToken from "../../../lib/utils/validateAccessToken";
import { getUserByAccessToken } from "../../../lib/db/users";
import { ObjectId, UUID } from "mongodb";
import locationManager from "../../../lib/classes/managers/LocationManager";
import { getAllShips } from "../../../lib/db/ships";
import DBShip from "../../../types/db/DBShip";
import { Planet } from "../../../lib/classes/managers/PlanetManager";
import MissionType from "../../../types/MissionType";
import { getAllResources } from "../../../lib/db/resources";
import FleetManager from "../../../lib/classes/managers/FleetManager";
import SystemManager from "../../../lib/classes/managers/SystemManager";
import getDistanceBetween from "../../../lib/utils/getDistanceBetween";

export const POST: APIRoute = async({ request }) => {
    const response = await validateAccessToken(request);
    if(response instanceof Response) return response;

    const userDB = await getUserByAccessToken(response);
    if(userDB === null) {
        return new Response(
            JSON.stringify({
                code: 401,
                message: "Unauthorized"
            }), { status: 401 }
        )
    }

    const user = locationManager.getUser(userDB._id);
    if(!user) {
        return new Response(
            JSON.stringify({
                code: 401,
                message: "Unauthorized"
            }), { status: 401 }
        )
    }

    let body: { source: string, destination: string, ships: Array<{ id: string, amount: number }>, cargo: Array<{ id: string, amount: number }>, mission: MissionType, currentSystem?: string };
    try {
        body = await request.json()
    } catch(e) {
        return new Response(
            JSON.stringify({
                code: 400,
                message: "Bad Request",
                error: "Invalid JSON body"
            }), { status: 400 }
        )
    }

    const checkSource = checkPlanetOrSystemId(body.source, 'source');
    if(typeof checkSource.error !== "undefined") return new Response(JSON.stringify(checkSource), { status: checkSource.code });
    const source = checkSource.planetOrSystem;

    const shipsDB = await getAllShips();

    const checkShipsBody = checkShips(body.ships, shipsDB, source);
    if(typeof checkShipsBody.error !== "undefined") return new Response(JSON.stringify(checkShipsBody), { status: checkShipsBody.code });

    const resourcesDB = await getAllResources();
    await source.resources.calculateCurrentAvailableResources();

    const checkCargoBody = checkCargo(body.cargo, body.ships, source, body.mission);
    if(typeof checkCargoBody.error !== "undefined") return new Response(JSON.stringify(checkCargoBody), { status: checkCargoBody.code });

    let dest, additionalData;
    if(body.mission === "EXPEDITION") {
        const destinationSector = checkSectorId(body.destination);
        if(typeof destinationSector.error !== "undefined") return new Response(JSON.stringify(destinationSector), { status: destinationSector.code });
        dest = destinationSector.sector;
    } else if(body.mission === "MINE") {
        const checkAsteroid = checkAsteroidId(body.destination, body.currentSystem ?? "");
        if(typeof checkAsteroid.error !== "undefined") return new Response(JSON.stringify(checkAsteroid), { status: checkAsteroid.code });
        dest = checkAsteroid.system;
        additionalData = checkAsteroid.asteroid;
    } else {
        const checkDestination = checkPlanetOrSystemId(body.destination, 'destination');
        if(typeof checkDestination.error !== "undefined") return new Response(JSON.stringify(checkDestination), { status: checkDestination.code });
        dest = checkDestination.planetOrSystem;
    }

    const flightTime = getDistanceBetween(source, dest);

    const fleetManager = new FleetManager(
        new ObjectId(),
        source,
        dest,
        new Date(),
        new Date(Date.now() + 1000 * flightTime),
        false,
        body.mission,
        body.ships,
        body.cargo,
        body.mission === "MINE" ? additionalData?.id.toString() : ""
    );

    const resourceDiff = await source.resources.getDifference(body.cargo.map(c => ({ id: c.id, amount: c.amount })));
    
    for(const res of resourceDiff) {
        if(res.amount < 0) {
            return new Response(
                JSON.stringify({
                    code: 400,
                    message: "Bad Request",
                    error: "Not enough resource with ID '" + res.id + "' on source planet"
                }), { status: 400 }
            )
        }
    }

    source.resources.setAmount(resourceDiff);
    await source.resources.sync();

    for(const ship of body.ships) {
        source.ships.removeShips(ship.id, ship.amount);
    }
    await source.ships.sync();
    await fleetManager.sync();

    locationManager.addFleet(fleetManager);
    
    return new Response(
        JSON.stringify({
            code: 200,
            message: "OK"
        }), { status: 200 }
    )
}

function checkPlanetOrSystemId(id: string, type: string) {
    if(typeof id === "undefined") return {
        code: 400,
        message: "Bad Request",
        error: `Missing '${type}' in body`
    }

    let idToCheck;
    try {
        idToCheck = new ObjectId(id);
    } catch(e) {
        return {
            code: 400,
            message: "Bad Request",
            error: `Invalid ID in '${type}'`
        }
    }

    const planetOrSystem = locationManager.findId(idToCheck);

    if(!planetOrSystem) return {
        code: 404,
        message: "Not Found",
        error: `Non-existent planet or system provided in '${type}'`
    }

    return {
        code: 200,
        message: "OK",
        planetOrSystem
    }
}

function checkAsteroidId(id: string, systemId: string) {
    if(typeof id === "undefined") return {
        code: 400,
        message: "Bad Request",
        error: `Missing 'destination' in body`
    }

    if(systemId === "") return {
        code: 400,
        message: "Bad Request",
        error: "Missing 'currentSystem' in body"
    }

    let idToCheck;
    try {
        idToCheck = new UUID(id);
    } catch(e) {
        return {
            code: 400,
            message: "Bad Request",
            error: `Invalid UUID in 'destination'`
        }
    }

    let systemObjectId;
    try {
        systemObjectId = new ObjectId(systemId);
    } catch(e) {
        return {
            code: 400,
            message: "Bad Request",
            error: "Invalid ID in 'currentSystem'"
        }
    }

    const system = locationManager.getSystem(systemObjectId);

    if(!system) return {
        code: 404,
        message: "Not Found",
        error: `Non-existent system provided in 'currentSystem'`
    }

    const asteroid = system.asteroids.asteroids.find(asteroid => asteroid.id.equals(idToCheck));
    if(!asteroid) return {
        code: 404,
        message: "Not Found",
        error: "Non-existent asteroid provided in 'destination'"
    }

    return {
        code: 200,
        message: "OK",
        asteroid,
        system
    }
}

function checkSectorId(id: string) {
    if(typeof id === "undefined") return {
        code: 400,
        message: "Bad Request",
        error: "Missing 'sector' in body"
    }

    let idToCheck;
    try {
        idToCheck = new ObjectId(id);
    } catch(e) {
        return {
            code: 400,
            message: "Bad Request",
            error: "Invalid ID in 'sector'"
        }
    }

    const sector = locationManager.getSector(idToCheck);

    if(!sector) return {
        code: 404,
        message: "Not Found",
        error: "Non-existent sector provided in 'sector'"
    }

    return {
        code: 200,
        message: "OK",
        sector
    }
}

function checkShips(ships: Array<{ id: string, amount: number }>, shipsDB: Array<DBShip>, source: Planet | SystemManager) {
    if(typeof ships === "undefined") return {
        code: 400,
        message: "Bad Request",
        error: "Missing 'ships' in body"
    }

    for(let i = 0; i < ships.length; i++) {
        if(typeof ships[i].id === "undefined" || ships[i].id === "") return {
            code: 400,
            message: "Bad Request",
            error: "Missing ship ID at position " + i
        }

        if(!shipsDB.find(ship => ship.id === ships[i].id)) return {
            code: 404,
            message: "Not Found",
            error: "Non-existent ship ID '" + ships[i].id + "' at position " + i
        }

        if(typeof ships[i].amount === "undefined") return {
            code: 400,
            message: "Bad Request",
            error: "Missing ship amount for ID '" + ships[i].id + "' at position " + i
        }

        if(ships[i].amount % 1 !== 0 || ships[i].amount < 0) return {
            code: 400,
            message: "Bad Request",
            error: "Ship amount for ID '" + ships[i].id + "' is not a non-negative integer at position " + i
        }

        if(ships[i].amount > (source.ships.getShipById(ships[i].id)?.amount ?? 0)) return {
            code: 400,
            message: "Bad Request",
            error: "Not enough ships on planet with ID '" + ships[i].id + "' at position " + i
        }
    }

    return {
        code: 200,
        message: "OK"
    }
}

function checkCargo(cargo: Array<{ id: string, amount: number }>, ships: Array<{ id: string, amount: number }>, source: Planet | SystemManager, missionType: MissionType) {
    if(missionType === "TRANSPORT" && cargo.length === 0) return {
        code: 400,
        message: "Bad Request",
        error: "Missing 'cargo' in body for requested mission 'TRANSPORT'"
    }

    for(let i = 0; i < cargo.length; i++) {
        if(typeof cargo[i].id === "undefined") return {
            code: 400,
            message: "Bad Request",
            error: "Missing resource ID at position " + i
        }

        if(!source.resources.resourcesDB.find(resource => resource.id === cargo[i].id)) return {
            code: 404,
            message: "Not Found",
            error: "Non-existent resource ID '" + cargo[i].id + "' at position " + i
        }

        if(typeof cargo[i].amount === "undefined") return {
            code: 400,
            message: "Bad Request",
            error: "Missing resource amount for ID '" + cargo[i].id + "' at position " + i
        }

        if(cargo[i].amount % 1 !== 0 || cargo[i].amount < 0) return {
            code: 400,
            message: "Bad Request",
            error: "Resource amount for ID '" + cargo[i].id + "' is not a non-negative integer at position " + i
        }

        if(cargo[i].amount > (source.resources.resources.find(res => res.id === cargo[i].id)?.amount ?? 0)) return {
            code: 400,
            message: "Bad Request",
            error: "Not enough resources on planet or system with ID '" + cargo[i].id + "' at position " + i
        }
    }

    const totalCargoAvailable = {
        solid: ships.reduce((acc, ship) => acc + (source.ships.shipsDB.find(s => s.id === ship.id)?.capacity.solid ?? 0) * ship.amount, 0),
        liquid: ships.reduce((acc, ship) => acc + (source.ships.shipsDB.find(s => s.id === ship.id)?.capacity.liquid ?? 0) * ship.amount, 0),
        gas: ships.reduce((acc, ship) => acc + (source.ships.shipsDB.find(s => s.id === ship.id)?.capacity.gas ?? 0) * ship.amount, 0)
    }

    const totalCargoUsed = {
        solid: cargo.reduce((acc, resource) => source.resources.resourcesDB.find(res => res.id === resource.id)?.type === "solid" ? acc + resource.amount : acc, 0),
        liquid: cargo.reduce((acc, resource) => source.resources.resourcesDB.find(res => res.id === resource.id)?.type === "liquid" ? acc + resource.amount : acc, 0),
        gas: cargo.reduce((acc, resource) => source.resources.resourcesDB.find(res => res.id === resource.id)?.type === "gas" ? acc + resource.amount : acc, 0)
    }

    if(totalCargoUsed.solid > totalCargoAvailable.solid) return {
        code: 400,
        message: "Bad Request",
        error: "Not enough solid cargo capacity on ships"
    }

    if(totalCargoUsed.liquid > totalCargoAvailable.liquid) return {
        code: 400,
        message: "Bad Request",
        error: "Not enough liquid cargo capacity on ships"
    }

    if(totalCargoUsed.gas > totalCargoAvailable.gas) return {
        code: 400,
        message: "Bad Request",
        error: "Not enough gas cargo capacity on ships"
    }

    //TODO: check for fuel

    return {
        code: 200,
        message: "OK"
    }
}