import { ObjectId } from "mongodb";
import { getAllFleet } from "../../db/fleet";
import { getAllGalaxies } from "../../db/galaxies";
import { getSectorById } from "../../db/sectors";
import { getSystemById } from "../../db/systems";
import { getAllUsers } from "../../db/users";
import User from "../User";
import FleetManager from "./FleetManager";
import { Planet } from "./PlanetManager";
import SystemManager, { System } from "./SystemManager";

export type Galaxy = {
    _id: ObjectId,
    name: string,
    sectors: Array<Sector>
    coords: {
        x: number,
        y: number
    }
}

export type Sector = {
    _id: ObjectId,
    galaxy: Galaxy,
    name: string,
    expedition: null,
    systems: Array<SystemManager>
}

class LocationManager {
    private static instance: LocationManager;

    private constructor() {}

    public static getInstance(): LocationManager {
        if (!LocationManager.instance) {
            LocationManager.instance = new LocationManager();
        }
        return LocationManager.instance;
    }

    galaxies: Galaxy[] = [];
    users: User[] = [];
    fleet: FleetManager[] = [];
    
    async init() {
        const currentTime = new Date();

        this.galaxies = [];
        this.users = [];
        this.fleet = [];

        const users = await getAllUsers();
        users.forEach(async user => {
            this.users.push(new User(user._id, user.username, user.email, user.createdAt, user.updatedAt, user.lastLogin));
        });

        console.log(`Loaded ${users.length} users from database`);

        const galaxies = await getAllGalaxies();
        for(const galaxy of galaxies) {
            const galaxyObject: Galaxy = {
                _id: galaxy._id,
                name: galaxy.name,
                sectors: [],
                coords: galaxy.coords
            };

            for(const sectorId of galaxy.sectors) {
                const sectorData = await getSectorById(sectorId);

                const sectorObject: Sector = {
                    _id: sectorData._id,
                    galaxy: galaxyObject,
                    name: sectorData.name,
                    expedition: null,
                    systems: []
                };

                for(const systemId of sectorData.systems) {
                    const systemData = await getSystemById(systemId);
                    const user = this.users.find(user => user.id.equals(systemData.ownedBy));

                    if(!user) throw new Error("User not found");

                    const systemObject: System = {
                        _id: systemData._id,
                        sector: sectorObject,
                        name: systemData.name,
                        ownedBy: user,
                        //@ts-ignore
                        structures: null,
                        //@ts-ignore
                        resources: null,
                        //@ts-ignore
                        ships: null,
                        planets: [],
                        asteroids: [],
                    };

                    const s = await new SystemManager(systemObject).fillData(systemData);

                    sectorObject.systems.push(s);
                };

                console.log(`Loaded ${sectorObject.systems.length} systems from sector ${sectorObject.name}`);

                galaxyObject.sectors.push(sectorObject);
            };

            console.log(`Loaded ${galaxyObject.sectors.length} sectors from galaxy ${galaxyObject.name}`);

            this.galaxies.push(galaxyObject);
        };

        console.log(`Loaded ${this.galaxies.length} galaxies from database`);

        for(const user of this.users) {
            await user.init();
            const userDB = users.find(u => u._id.equals(user.id));
            const mainPlanet = this.getPlanet(userDB?.mainPlanet as ObjectId);

            if(!mainPlanet) throw new Error(`Main planet not found for user ${user.username} (${user.id})`);

            user.mainPlanet = mainPlanet;
        };

        const fleets = await getAllFleet();
        for(const fleet of fleets) {
            if(fleet.arrivalTime > currentTime) {
                const source = this.getPlanet(fleet.source) || this.getSystem(fleet.source);
                const destination = this.getPlanet(fleet.destination) || this.getSystem(fleet.destination);
                
                if(source && destination) this.fleet.push(new FleetManager(
                    fleet._id,
                    source,
                    destination,
                    fleet.departureTime,
                    fleet.arrivalTime,
                    fleet.returning,
                    fleet.mission,
                    fleet.ships,
                    fleet.cargo,
                    fleet.additionalData
                ));
            }
        }

        console.log(`Loaded ${this.fleet.length} active fleet from database`);

        return this;
    }

    findId(id: ObjectId) {
        let found: SystemManager | Planet | null = null;

        for(const galaxy of this.galaxies) {
            for(const sector of galaxy.sectors) {
                for(const system of sector.systems) {
                    if(system.data._id.equals(id)) {
                        found = system;
                        break;
                    }

                    for(const planet of system.planets) {
                        if(planet._id.equals(id)) {
                            found = planet;
                            break;
                        }
                    }
                }
            }
        }

        return found;
    }

    getGalaxy(_id: ObjectId) {
        return this.galaxies.find(galaxy => galaxy._id.equals(_id));
    }

    getSector(_id: ObjectId) {
        let foundSector: Sector | undefined;
        this.galaxies.find(galaxy => galaxy.sectors.find(sector => {
            if(sector._id.equals(_id)) foundSector = sector;
        }));
        return foundSector;
    }

    getSystem(_id: ObjectId) {
        let foundSystem: SystemManager | undefined;
        this.galaxies.find(galaxy => galaxy.sectors.find(sector => sector.systems.find(system => {
            if(system.data._id.equals(_id)) foundSystem = system
        })));
        return foundSystem
    }

    getSystemsOwnedBy(id: ObjectId) {
        const systems: SystemManager[] = [];

        for(const galaxy of this.galaxies) {
            for(const sector of galaxy.sectors) {
                for(const system of sector.systems) {
                    if(system.data.ownedBy.id.equals(id)) systems.push(system);
                }
            }
        }

        return systems;
    }

    getPlanet(_id: ObjectId) {
        let foundPlanet: Planet | undefined;

        for(const galaxy of this.galaxies) {
            for(const sector of galaxy.sectors) {
                for(const system of sector.systems) {
                    foundPlanet = system.planets.find(planet => planet._id.equals(_id));
                    if(foundPlanet) break;
                }
                if(foundPlanet) break;
            }
            if(foundPlanet) break;
        }

        return foundPlanet;
    }

    getUser(_id: ObjectId) {
        return this.users.find(user => user.id.equals(_id));
    }

    async updateFleet() {
        for(const fleet of this.fleet) {
            const { finished } = await fleet.checkStatus();
            if(finished) {
                this.fleet = this.fleet.filter(f => !f.data.id.equals(fleet.data.id));
            }
        }
    }

    async getAllFleetByUserId(userId: string) {
        await this.updateFleet();
        return userId === '' ? [] : this.fleet.filter(fleet => {
            let found = false;

            if(fleet.data.source instanceof SystemManager) found = fleet.data.source.data.ownedBy.id.equals(userId);
            else found = fleet.data.source.system.data.ownedBy.id.equals(userId);

            if(fleet.data.destination instanceof SystemManager) found = fleet.data.destination.data.ownedBy.id.equals(userId);
            else if(!("expedition" in fleet.data.destination)) found = fleet.data.destination.system.data.ownedBy.id.equals(userId);
            
            return found;
        });
    }

    addFleet(fleet: FleetManager) {
        this.fleet.push(fleet);
    }
}

const locationManager = LocationManager.getInstance();
await locationManager.init();
export default locationManager;