package service;

import client.BuddyList;
import client.Client;
import client.inventory.InventoryType;
import config.YamlConfig;
import constants.id.MapId;
import database.character.CharacterSaver;
import net.netty.LoginServer;
import net.server.Server;
import net.server.coordinator.session.SessionCoordinator;
import net.server.guild.Guild;
import net.server.guild.GuildCharacter;
import net.server.guild.GuildPackets;
import net.server.world.MessengerCharacter;
import net.server.world.Party;
import net.server.world.PartyCharacter;
import net.server.world.PartyOperation;
import net.server.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scripting.event.EventInstanceManager;
import server.ThreadManager;
import server.maps.FieldLimit;
import server.maps.MapleMap;
import server.maps.MiniDungeonInfo;
import tools.PacketCreator;

import java.io.IOException;
import java.net.InetAddress;

public class TransitionService {
    private static final Logger log = LoggerFactory.getLogger(TransitionService.class);
    private final Server server = Server.getInstance();
    private final CharacterSaver chrSaver;
    private final AccountService accountService;

    public TransitionService(CharacterSaver characterSaver, AccountService accountService) {
        this.chrSaver = characterSaver;
        this.accountService = accountService;
    }

    public void changeChannel(Client c, int channel) {
        var chr = c.getPlayer();
        if (chr.isBanned()) {
            disconnect(c, false);
            return;
        }

        if (!chr.isAlive() || FieldLimit.CANNOTMIGRATE.check(chr.getMap().getFieldLimit())) {
            c.sendPacket(PacketCreator.enableActions());
            return;
        }

        if (MiniDungeonInfo.isDungeonMap(chr.getMapId())) {
            c.sendPacket(PacketCreator.serverNotice(5, "Changing channels or entering Cash Shop or MTS are disabled when inside a Mini-Dungeon."));
            c.sendPacket(PacketCreator.enableActions());
            return;
        }

        String[] socket = Server.getInstance().getInetSocket(c, c.getWorld(), channel);
        if (socket == null) {
            c.sendPacket(PacketCreator.serverNotice(1, "Channel " + channel + " is currently disabled. Try another channel."));
            c.sendPacket(PacketCreator.enableActions());
            return;
        }

        chr.closePlayerInteractions();
        chr.closePartySearchInteractions();

        chr.unregisterChairBuff();
        server.getPlayerBuffStorage().addBuffsToStorage(chr.getId(), chr.getAllBuffs());
        server.getPlayerBuffStorage().addDiseasesToStorage(chr.getId(), chr.getAllDiseases());
        chr.setDisconnectedFromChannelWorld();
        chr.notifyMapTransferToPartner(-1);
        chr.removeIncomingInvites();
        chr.cancelAllBuffs(true);
        chr.cancelAllDebuffs();
        chr.cancelBuffExpireTask();
        chr.cancelDiseaseExpireTask();
        chr.cancelSkillCooldownTask();
        chr.cancelQuestExpirationTask();
        //Cancelling magicdoor? Nope
        //Cancelling mounts? Noty

        chr.getInventory(InventoryType.EQUIPPED).checked(false); //test
        chr.getMap().removePlayer(chr);
        c.getChannelServer().removePlayer(chr);

        chrSaver.save(chr);

        setInTransition(chr.getClient(), chr.getId());
        try {
            c.sendPacket(PacketCreator.getChannelChange(InetAddress.getByName(socket[0]), Integer.parseInt(socket[1])));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void setInTransition(Client c, int chrId) {
        accountService.setInTransition(c);
        Server.getInstance().setCharacteridInTransition(c, chrId);
    }

    public void disconnect(final Client c, final boolean shutdown) {
        if (c.tryDisconnect()) {
            ThreadManager.getInstance().newTask(() -> disconnectInternal(c, shutdown));
        }
    }

    public void forceDisconnect(Client c) {
        if (c.tryDisconnect()) {
            disconnectInternal(c, true);
        }
    }

    private void disconnectInternal(Client c, boolean shutdown) {
        final var chr = c.getPlayer();
        if (chr != null && chr.isLoggedin() && chr.getClient() != null) {
            chr.cancelMagicDoor();

            final World wserv = c.getWorldServer();   // obviously wserv is NOT null if this chr was online on it
            try {
                removePlayer(c, wserv, c.isInTransition());

                final int channel = c.getChannel();
                if (!(channel == LoginServer.CHANNEL_ID || shutdown)) {
                    if (!c.isInTransition()) { // meaning not changing channels
                        final int messengerid = chr.getMessenger() == null ? 0 : chr.getMessenger().getId();
                        if (messengerid > 0) {
                            wserv.leaveMessenger(messengerid, new MessengerCharacter(chr, 0));
                        }

                        chr.forfeitExpirableQuests();    //This is for those quests that you have to stay logged in for a certain amount of time

                        Guild guild = chr.getGuild();
                        if (guild != null) {
                            final Server server = Server.getInstance();
                            server.setGuildMemberOnline(chr, false, chr.getClient().getChannel());
                            chr.sendPacket(GuildPackets.showGuildInfo(chr));
                        }

                        BuddyList buddyList = chr.getBuddylist();
                        if (buddyList != null) {
                            wserv.loggedOff(chr.getName(), chr.getId(), channel, chr.getBuddylist().getBuddyIds());
                        }
                    }
                }
            } catch (final Exception e) {
                log.error("Account stuck", e);
            } finally {
                if (!c.isInTransition()) {
                    final GuildCharacter guildChr = chr.getMGC();
                    if (guildChr != null) {
                        guildChr.setCharacter(null);
                    }
                    wserv.removePlayer(chr);
                    //getChannelServer().removePlayer(player); already being done

                    chr.cancelAllDebuffs();
                    chrSaver.save(chr);

                    chr.logOff();
                    if (YamlConfig.config.server.INSTANT_NAME_CHANGE) {
                        chr.doPendingNameChange();
                    }
                    c.clear();
                } else {
                    c.getChannelServer().removePlayer(chr);

                    chr.cancelAllDebuffs();
                    chrSaver.save(chr);
                }
            }
        }

        SessionCoordinator.getInstance().closeSession(c, false);


        if (!c.isInTransition() && c.isLoggedIn()) {
            accountService.setLoggedOut(c);
            c.clear();
        } else {
            if (!Server.getInstance().hasCharacteridInTransition(c)) {
                accountService.setLoggedOut(c);
            }

            c.clearEngines();
        }
    }

    private void removePlayer(Client c, World world, boolean serverTransition) {
        var chr = c.getPlayer();
        try {
            chr.setDisconnectedFromChannelWorld();
            chr.notifyMapTransferToPartner(-1);
            chr.removeIncomingInvites();
            chr.cancelAllBuffs(true);

            chr.closePlayerInteractions();
            chr.closePartySearchInteractions();

            if (!serverTransition) {    // thanks MedicOP for detecting an issue with party leader change on changing channels
                removePartyPlayer(c, world);

                EventInstanceManager eim = chr.getEventInstance();
                if (eim != null) {
                    eim.playerDisconnected(chr);
                }

                if (chr.getMonsterCarnival() != null) {
                    chr.getMonsterCarnival().playerDisconnected(chr.getId());
                }

                if (chr.getAriantColiseum() != null) {
                    chr.getAriantColiseum().playerDisconnected(chr);
                }
            }

            if (chr.getMap() != null) {
                int mapId = chr.getMapId();
                chr.getMap().removePlayer(chr);
                if (MapId.isDojo(mapId)) {
                    c.getChannelServer().freeDojoSectionIfEmpty(mapId);
                }

                if (chr.getMap().getHPDec() > 0) {
                    world.removePlayerHpDecrease(chr);
                }
            }

        } catch (final Throwable t) {
            log.error("Account stuck", t);
        }
    }

    private void removePartyPlayer(Client c, World world) {
        var chr = c.getPlayer();
        MapleMap map = chr.getMap();
        final Party party = chr.getParty();
        final int idz = chr.getId();

        if (party != null) {
            final PartyCharacter chrp = new PartyCharacter(chr);
            chrp.setOnline(false);
            world.updateParty(party.getId(), PartyOperation.LOG_ONOFF, chrp);
            if (party.getLeader().getId() == idz && map != null) {
                PartyCharacter lchr = null;
                for (PartyCharacter pchr : party.getMembers()) {
                    if (pchr != null && pchr.getId() != idz && (lchr == null || lchr.getLevel() <= pchr.getLevel()) && map.getCharacterById(pchr.getId()) != null) {
                        lchr = pchr;
                    }
                }
                if (lchr != null) {
                    world.updateParty(party.getId(), PartyOperation.CHANGE_LEADER, lchr);
                }
            }
        }
    }
}
