package org.toop.framework.networking.connection.handlers;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.toop.framework.eventbus.bus.EventBus;
import org.toop.framework.networking.connection.events.NetworkEvents;

public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
    private static final Logger logger = LogManager.getLogger(NetworkingGameClientHandler.class);

    private final EventBus eventBus;
    private final long connectionId;

    public NetworkingGameClientHandler(EventBus eventBus, long connectionId) {
        this.eventBus = eventBus;
        this.connectionId = connectionId;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        String rec = msg.toString().trim();

        if (rec.equalsIgnoreCase("err")) {
            logger.error("server-{} send back error, data: {}", ctx.channel().remoteAddress(), msg);
            return;
        }
        if (rec.equalsIgnoreCase("ok")) {
            logger.info(
                    "Received OK message from server-{}, data: {}",
                    ctx.channel().remoteAddress(),
                    msg);
            return;
        }
        if (rec.toLowerCase().startsWith("svr")) {
            logger.info(
                    "Received SVR message from server-{}, data: {}",
                    ctx.channel().remoteAddress(),
                    msg);
            eventBus.post(new NetworkEvents.ServerResponse(this.connectionId));
            parseServerReturn(rec);
            return;
        }
        logger.info(
                "Received unparsed message from server-{}, data: {}",
                ctx.channel().remoteAddress(),
                msg);
    }

    private void parseServerReturn(String rec) {

        String recSrvRemoved = rec.substring("SVR ".length());
        Pattern gamePattern = Pattern.compile("GAME (\\w+)", Pattern.CASE_INSENSITIVE);
        Matcher gameMatch = gamePattern.matcher(recSrvRemoved);

        if (gameMatch.find()) {
            switch (gameMatch.group(1)) {
                case "YOURTURN":
                    gameYourTurnHandler(recSrvRemoved);
                    return;
                case "MOVE":
                    gameMoveHandler(recSrvRemoved);
                    return;
                case "MATCH":
                    gameMatchHandler(recSrvRemoved);
                    return;
                case "CHALLENGE":
                    gameChallengeHandler(recSrvRemoved);
                    return;
                case "WIN", "DRAW", "LOSS":
                    gameWinConditionHandler(recSrvRemoved);
                    return;
                default:
                    // return
            }
        } else {

            Pattern getPattern = Pattern.compile("(\\w+)", Pattern.CASE_INSENSITIVE);
            Matcher getMatch = getPattern.matcher(recSrvRemoved);

            if (getMatch.find()) {
                switch (getMatch.group(1)) {
                    case "PLAYERLIST":
                        playerlistHandler(recSrvRemoved);
                        return;
                    case "GAMELIST":
                        gamelistHandler(recSrvRemoved);
                        return;
                    case "HELP":
                        helpHandler(recSrvRemoved);
                        return;
                    case "RESULTS":
                        resultsHandler(recSrvRemoved);
                        return;
                    default:
                        // return
                }
            } else {
                logger.error("Could not parse: {}", rec);
            }
        }
    }

    private static String extract(String input, String key) {
        Pattern p = Pattern.compile(
                key + "\\s*:\\s*(\\[[^]]*]|\"[^\"]*\")",
                Pattern.CASE_INSENSITIVE
        );
        Matcher m = p.matcher(input);
        return m.find() ? m.group(1) : null;
    }

    private void resultsHandler(String rec) {
        // TODO all of this

        IO.println(rec);

        String gameTypeRaw = extract(rec, "GAMETYPE");
        String usersRaw = extract(rec, "USERS");
        String scoreTypesRaw = extract(rec, "SCORETYPES");
        String scoresRaw = extract(rec, "SCORES");

        if (usersRaw == null) return;

        String[] users;
        if (usersRaw.length() > 2) {
            users = Arrays.stream(usersRaw.substring(1, usersRaw.length() - 1).split(","))
                .map(s -> s.trim().replace("\"", ""))
                .toArray(String[]::new);
        } else {
            users = new String[]{};
        }

        String[] scoreTypes;
        if (scoreTypesRaw.length() > 2) {
            scoreTypes = Arrays.stream(scoreTypesRaw.substring(1, usersRaw.length() - 1).split(","))
                    .map(s -> s.trim().replace("\"", ""))
                    .toArray(String[]::new);
        } else {
            scoreTypes = new String[]{};
        }

        if (scoresRaw == null) return;
        if (scoresRaw.length() > 2) {
            List<Integer[]> scores = Arrays.stream(
                            scoresRaw.substring(1, scoresRaw.length() - 1) // remove outer []
                                    .split("\\],\\[")
                    )
                    .map(part -> part.replace("[", "").replace("]", ""))
                    .map(part -> Arrays.stream(part.split(","))
                            .map(String::trim)
                            .map(Integer::parseInt)
                            .toArray(Integer[]::new)
                    )
                    .toList();

            eventBus.post(new NetworkEvents.TournamentResultResponse(this.connectionId, gameTypeRaw, users, scoreTypes, scores));
        } else {
            eventBus.post(new NetworkEvents.TournamentResultResponse(this.connectionId, gameTypeRaw, users, scoreTypes, new ArrayList<>()));
        }

    }

    private void gameMoveHandler(String rec) {
        String[] msg =
                Pattern.compile(
                                "(?:player|details|move):\\s*\"?([^\",}]+)\"?",
                                Pattern.CASE_INSENSITIVE)
                        .matcher(rec)
                        .results()
                        .map(m -> m.group(1).trim())
                        .toArray(String[]::new);

        eventBus.post(new NetworkEvents.GameMoveResponse(this.connectionId, msg[0], msg[1], msg[2]));
    }

    private void gameWinConditionHandler(String rec) {
        String condition = Pattern.compile("\\b(win|draw|loss)\\b", Pattern.CASE_INSENSITIVE)
                .matcher(rec)
                .results()
                .map(MatchResult::group)
                .findFirst()
                .orElse("");

        eventBus.post(new NetworkEvents.GameResultResponse(this.connectionId, condition));
    }

    private void gameChallengeHandler(String rec) {
        boolean isCancelled = rec.toLowerCase().startsWith("challenge accepted");
        try {
            String[] msg =
                    Pattern.compile(
                                    "(?:CHALLENGER|GAMETYPE|CHALLENGENUMBER):\\s*\"?(.*?)\"?\\s*[,}]")
                            .matcher(rec)
                            .results()
                            .map(m -> m.group().trim())
                            .toArray(String[]::new);

            if (isCancelled)
                eventBus.post(new NetworkEvents.GameResultResponse(this.connectionId, msg[0]));
            else
                eventBus.post(new NetworkEvents.ChallengeResponse(this.connectionId, msg[0], msg[1], msg[2]));
        } catch (ArrayIndexOutOfBoundsException e) {
            logger.error("Array out of bounds for: {}", rec, e);
        }
    }

    private void gameMatchHandler(String rec) {
        try {
            String[] msg =
                    Pattern.compile("\"([^\"]*)\"")
                            .matcher(rec)
                            .results()
                            .map(m -> m.group(1).trim())
                            .toArray(String[]::new);

            // [0] playerToMove, [1] gameType, [2] opponent
            eventBus.post(new NetworkEvents.GameMatchResponse(this.connectionId, msg[0], msg[1], msg[2]));
        } catch (ArrayIndexOutOfBoundsException e) {
            logger.error("Array out of bounds for: {}", rec, e);
        }
    }

    private void gameYourTurnHandler(String rec) {
        @SuppressWarnings("StreamToString")
        String msg =
                Pattern.compile("TURNMESSAGE:\\s*\"([^\"]*)\"")
                        .matcher(rec)
                        .results()
                        .toString()
                        .trim();

        eventBus.post(new NetworkEvents.YourTurnResponse(this.connectionId, msg));
    }

    private void playerlistHandler(String rec) {
        String[] players =
                Pattern.compile("\"([^\"]+)\"")
                        .matcher(rec)
                        .results()
                        .map(m -> m.group(1).trim())
                        .toArray(String[]::new);

        eventBus.post(new NetworkEvents.PlayerlistResponse(this.connectionId, players));
    }

    private void gamelistHandler(String rec) {
        String[] gameTypes =
                Pattern.compile("\"([^\"]+)\"")
                        .matcher(rec)
                        .results()
                        .map(m -> m.group(1).trim())
                        .toArray(String[]::new);

        eventBus.post(new NetworkEvents.GamelistResponse(this.connectionId, gameTypes));
    }

    private void helpHandler(String rec) {
        logger.info(rec);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        logger.error(cause.getMessage(), cause);
        ctx.close();
    }
}
