package com.alterdekim.hearthhack.component.processor;


import com.alterdekim.Protocol;
import com.alterdekim.hearthhack.component.TcpConnection;
import com.alterdekim.hearthhack.component.processor.client.request.ClientRequestParser;
import com.alterdekim.hearthhack.reflect.ReflectionLoader;
import com.alterdekim.hearthhack.util.*;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.Optional;


@Slf4j
public class GameUtilitiesProcessor extends Processor {

    private ReflectionLoader<ClientRequestParser> parsers;

    public GameUtilitiesProcessor() {
        this.setProcessorId(9);
        this.parsers = new ReflectionLoader<>();
        this.parsers.initReflect(ClientRequestParser.class);
    }

    private ClientRequestBody parseClientRequest(Protocol.ClientRequest request) {
        Optional<Protocol.Attribute> at = request.getAttributeList()
                .stream()
                .filter(a -> a.hasName() && a.getName().equals("p"))
                .findFirst();
        Optional<Protocol.Attribute> at1 = request.getAttributeList()
                .stream()
                .filter(a -> a.hasName() && a.getName().equals("r"))
                .findFirst();
        if(at.isEmpty()) return null;
        byte[] b = at.get().getValue().getBlobValue().toByteArray();
        ClientRequestBody requestBody = null;
        try {
            requestBody = ClientRequestManager.GetUtilPacketFromBytes(b);
        } catch (InvalidProtocolBufferException e) {
            log.error(e.getMessage());
        }
        if(at1.isPresent() && requestBody != null) requestBody.setRoute(at1.get().getValue().getIntValue());
        return requestBody;
    }

    private Protocol.GenericResponse.Builder generateGenericResponse(int type) {
        return Protocol.GenericResponse.newBuilder()
                .setRequestId(type)
                .setResultCode(Protocol.GenericResponse.Result.RESULT_REQUEST_IN_PROCESS);
    }

    private void generateBasicNotification(int type, TcpConnection conn) throws Exception {
        sendNotificationWithResponse(generateGenericResponse(type).build(), conn);
    }

    private void generateNotificationWithSubId(int type, int subId, TcpConnection conn) throws Exception {
        sendNotificationWithResponse(generateGenericResponse(type).setRequestSubId(subId).build(), conn);
    }

    private void sendNotificationWithResponse(Protocol.GenericResponse gr, TcpConnection conn) throws Exception {
        Protocol.Notification n = GameUtilities.generateNotification(326, gr.toByteString(), gr.getSerializedSize());

        Protocol.Header header = Protocol.Header.newBuilder()
                .setServiceId(4)
                .setMethodId(1)
                .setToken(conn.nextToken())
                .setObjectId(0)
                .setSize(n.getSerializedSize())
                .setStatus(0)
                .build();

        conn.send(new BattleNetPacket(header, n.toByteArray()));
    }

    private void generateBunchOfNotificationsWithSubId(int type, ClientRequestBody body, TcpConnection conn) throws InvalidProtocolBufferException {
        Protocol.GenericRequestList genericRequestList = Protocol.GenericRequestList.parseFrom(body.getBody());
        genericRequestList.getRequestsList().forEach(gr -> {
            try {
                this.generateNotificationWithSubId(type, gr.getRequestSubId(), conn);
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        });
    }

    private void processClientRequest(BattleNetPacket packet, TcpConnection conn) throws Exception {
        Protocol.ClientRequest cr = Protocol.ClientRequest.parseFrom(packet.getBody());

        ClientRequestBody p = parseClientRequest(cr);
        int type = p != null ? p.getType() : 0;

        if( type != 314 && type != 255 && type != 214 ) {
            Protocol.GenericResponse genericResponse = Protocol.GenericResponse
                    .newBuilder()
                    .setRequestId(type)
                    .setResultCode(Protocol.GenericResponse.Result.RESULT_REQUEST_IN_PROCESS)
                    .build();

            Protocol.ClientResponse clResponse = Protocol.ClientResponse.newBuilder()
                    .addAttribute(Protocol.Attribute.newBuilder()
                            .setName("id")
                            .setValue(Protocol.Variant.newBuilder()
                                    .setIntValue(326)
                            ))
                    .addAttribute(Protocol.Attribute.newBuilder()
                            .setName("proto")
                            .setValue(Protocol.Variant.newBuilder()
                                    .setBlobValue(genericResponse.toByteString())
                            ))
                    .build();

            Protocol.Header header = Processor.generateResponse(clResponse.getSerializedSize(), packet.getHeader().getToken(), 0, 0);
            conn.send(new BattleNetPacket(header, clResponse.toByteArray()));
        }

        switch(type) {
            case 201 -> generateNotificationWithSubId(type, Protocol.UtilGenericRequest.parseFrom(p.getBody()).getRequestSubId(), conn);
            case 327 -> generateBunchOfNotificationsWithSubId(type, p, conn);
            default -> generateBasicNotification(type, conn);
        }

        if( packet.getHeader().getToken() == 28 ) {
            byte[] b = Util.hexStringToByteArray("1252AA064F0A12094743545702000002116739AB040000000012190A170A0A08CE840110021809200012091884B6DAFCCEBDCC02121E0A1C0A0A08CE8401100218082000120E3A0C0D4743545715746174731804");

            Protocol.Header header = Protocol.Header.newBuilder()
                    .setServiceId(5)
                    .setMethodId(6)
                    .setToken(conn.nextToken())
                    .setObjectId(2)
                    .setSize(b.length)
                    .setStatus(0)
                    .build();

            conn.send(new BattleNetPacket(header, b));
        } else if( packet.getHeader().getToken() == 34 || packet.getHeader().getToken() == 35 ) {

            Protocol.Header header = Processor.generateResponse(0, 12, 0, 0);

            conn.send(new BattleNetPacket(header, new byte[0]));

            header = Processor.generateResponse(0, packet.getHeader().getToken(), 0, 0);

            conn.send(new BattleNetPacket(header, new byte[0]));
        }

        if( !this.parsers.getParsers().containsKey(type) ) {
            log.error("Unknown ClientRequest type #{}", type);
            return;
        }
        this.parsers.getParsers().get(type).parse(packet, p, conn);
    }

    @Override
    public void process(BattleNetPacket packet, TcpConnection conn) throws Exception {
        if( packet.getHeader().getMethodId() == 1 ) {
            processClientRequest(packet, conn);
            return;
        }
        log.error("Can't process weird GameUtilitiesProcessor method: {}", packet.getHeader().getMethodId());
    }

    @Override
    public String getExportName() {
        return "bnet.protocol.game_utilities.GameUtilities";
    }
}