// Copyright 2015-2016 Eivind Vegsundvåg
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ninja.eivind.hotsreplayuploader.providers.hotslogs;

import com.amazonaws.services.s3.AmazonS3;
import ninja.eivind.hotsreplayuploader.models.ReplayFile;
import ninja.eivind.hotsreplayuploader.models.Status;
import ninja.eivind.hotsreplayuploader.providers.Provider;
import ninja.eivind.hotsreplayuploader.utils.SimpleHttpClient;
import ninja.eivind.stormparser.models.Player;
import ninja.eivind.stormparser.models.PlayerType;
import ninja.eivind.stormparser.models.Replay;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * Implements a {@link Provider} to upload replays to hotslogs.com.<br>
 * Uses the Amazon AWS S3 data store to save the file
 * and an HTTP request to hotslogs.com to submit it to the site.
 */
@Component
public class HotsLogsProvider extends Provider {

    private static final Logger LOG = LoggerFactory.getLogger(HotsLogsProvider.class);

    private static final String CLIENT_ID = "HotSLogsUploaderFX";
    private static final String BASE_URL = "https://www.hotslogs.com/UploadFile?Source=" + CLIENT_ID;
    private static long maintenance;

    private final AmazonS3 s3Client;

    private SimpleHttpClient httpClient;

    @Autowired
    public HotsLogsProvider(SimpleHttpClient httpClient, @Qualifier("hotslogs-s3client") AmazonS3 s3Client) {
        super("HotSLogs.com");
        this.httpClient = httpClient;
        this.s3Client = s3Client;
    }

    public static boolean isMaintenance() {
        return maintenance + 600000L > System.currentTimeMillis();
    }

    private static UUID getUUIDForString(String concatenatedString) throws NoSuchAlgorithmException {
        final byte[] hashed = MessageDigest.getInstance("MD5").digest(concatenatedString.getBytes());
        final byte[] reArranged = reArrangeForUUID(hashed);
        return getUUID(reArranged);
    }

    private static byte[] reArrangeForUUID(byte[] hashed) {
        return new byte[]{
                hashed[3],
                hashed[2],
                hashed[1],
                hashed[0],

                hashed[5],
                hashed[4],
                hashed[7],
                hashed[6],
                hashed[8],
                hashed[9],
                hashed[10],
                hashed[11],
                hashed[12],
                hashed[13],
                hashed[14],
                hashed[15],
        };
    }

    private static UUID getUUID(byte[] bytes) {
        long msb = 0;
        long lsb = 0;
        assert bytes.length == 16 : "data must be 16 bytes in length";
        for (int i = 0; i < 8; i++) {
            msb = (msb << 8) | (bytes[i] & 0xff);
        }
        for (int i = 8; i < 16; i++) {
            lsb = (lsb << 8) | (bytes[i] & 0xff);
        }

        return new UUID(msb, lsb);
    }

    @Override
    public Status upload(final ReplayFile replayFile) {
        if (isMaintenance()) {
            return null;
        }

        final File file = replayFile.getFile();
        if (!(file.exists() && file.canRead())) {
            return Status.EXCEPTION;
        }

        final String fileName = UUID.randomUUID() + ".StormReplay";
        LOG.info("Assigning remote file name " + fileName + " to " + replayFile);
        final String uri = BASE_URL + "&FileName=" + fileName;

        return uploadFileToHotSLogs(file, fileName, uri);

    }

    @Override
    public Status getPreStatus(final Replay replay) {

        // Temporary fix for computer players found until the parser supports this
        if (replayHasComputerPlayers(replay)) {
            LOG.info("Computer players found for replay, tagging as uploaded.");
            return Status.UNSUPPORTED_GAME_MODE;
        }
        try {
            final String matchId = getMatchId(replay);
            LOG.info("Calculated matchId to be" + matchId);
            final String uri = BASE_URL + "&ReplayHash=" + matchId;
            final String result = getHttpClient().simpleRequest(uri).toLowerCase();
            if (result.equals("duplicate")) {
                return Status.UPLOADED;
            }
        } catch (NoSuchAlgorithmException e) {
            LOG.warn("Platform does not support MD5; cannot proceed with parsing", e);
        } catch (IOException e) {
            return Status.EXCEPTION;
        }
        return Status.NEW;
    }

    private Status uploadFileToHotSLogs(File file, String fileName, String uri) {
        try {
            s3Client.putObject("heroesreplays", fileName, file);
            LOG.info("File " + fileName + " uploaded to remote storage.");
            final String result = getHttpClient().simpleRequest(uri).toLowerCase();
            switch (result) {
                case "duplicate":
                case "success":
                    LOG.info("File registered with HotSLogs.com");
                    return Status.UPLOADED;
                case "prealphawipe":
                case "computerplayerfound":
                case "trymemode":
                    LOG.warn("File not supported by HotSLogs.com");
                    return Status.UNSUPPORTED_GAME_MODE;
                case "maintenance":
                    LOG.error("HotSLogs.com is currently undergoing maintenance.");
                    maintenance = System.currentTimeMillis();
                    return Status.NEW;
                default:
                    LOG.error("Could not upload file. Unknown status \"" + result + "\" received.");
                    return Status.EXCEPTION;
            }

        } catch (Exception e) {
            LOG.error("Could not upload file.", e);
            return Status.EXCEPTION;
        }
    }

    private boolean replayHasComputerPlayers(Replay replay) {
        return replay.getReplayDetails()
                .getPlayers()
                .stream()
                .map(Player::getPlayerType)
                .anyMatch(playerType -> playerType == PlayerType.COMPUTER);
    }

    protected String getMatchId(Replay replay) throws NoSuchAlgorithmException {
        final String concatenatedString = getConcatenatedString(replay);

        return getUUIDForString(concatenatedString).toString();

    }

    private String getConcatenatedString(Replay replay) {
        final String randomValue = String.valueOf(replay.getInitData().getRandomValue());
        final List<String> battleNetIdsSorted = replay.getReplayDetails()
                .getPlayers()
                .stream()
                .map(Player::getBNetId)
                .map(Long::parseLong)
                .sorted()
                .map(String::valueOf)
                .collect(Collectors.toList());
        final StringBuilder builder = new StringBuilder();
        battleNetIdsSorted.forEach(builder::append);
        builder.append(randomValue);
        return builder.toString();
    }

    public SimpleHttpClient getHttpClient() {
        return httpClient;
    }

    protected void setHttpClient(SimpleHttpClient httpClient) {
        this.httpClient = httpClient;
    }
}
