package ru.yandex.wmtools.common.sita;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;

/**
 * User: azakharov
 * Date: 18.03.14
 * Time: 14:30
 */
public class SitaMirroringJsonResponse {
    private static final Logger log = LoggerFactory.getLogger(SitaMirroringJsonResponse.class);

    private static final ObjectMapper OM = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    private static final ObjectWriter LOG_OBJECT_WRITER;

    static {
        SimpleFilterProvider filterProvider = new SimpleFilterProvider();
        filterProvider.addFilter(SitaJson.TURLFETCHINGRESULT_FILTER,
                SimpleBeanPropertyFilter.serializeAllExcept("Document", "RobotsTxt"));
        LOG_OBJECT_WRITER = OM.disable(SerializationFeature.WRITE_NULL_MAP_VALUES).writer(filterProvider);
    }

    public static SitaMirroringResponse parse(Reader reader) throws IOException {
        SitaJson.TResponse response = OM.readValue(reader, SitaJson.TResponse.class);
        return parseJson(response);
    }

    static SitaMirroringResponse parseJson(SitaJson.TResponse response) throws IOException {
        log.debug("Sita response: {}", LOG_OBJECT_WRITER.writeValueAsString(response));

        if (response.Errors != null && response.Errors.length > 0) {
            for (SitaJson.TError error : response.Errors) {
                if (error.SitaError != null) {
                    throw new SitaException("Sita Error " + error.SitaError.Code);
                }
            }
        }

        if (response.Results == null || response.Results.length == 0) {
            log.error("TResponse.Results is empty");
            throw new SitaException("Empty Sita results");
        }

        SitaJson.TActionResult result = null;
        for (SitaJson.TActionResult actionResult: response.Results) {
            if (actionResult.Type != SitaJson.EActionType.AT_MIRRORING) {
                log.warn("Found unknown action result: {}", actionResult.Type);
            } else {
                result = actionResult;
                break;
            }
        }
        if (result == null) {
            throw new SitaException("AT_MIRRORING Result not found");
        }
        if (result.MirroringResult == null) {
            throw new SitaException("Empty MirroringResult");
        }
        if (result.MirroringResult.Response == null) {
            throw new SitaException("Empty MirroringResult.Response");
        }

        SitaJson.TMirroringResponse mr = result.MirroringResult.Response;
        if (mr.Status == null) {
            throw new SitaException("Bad Sita Response: Status or Results field is empty");
        }

        List<SitaMirroringResponse.THostResult> hostResults = new ArrayList<>();
        if (mr.Results != null) {
            for (SitaJson.TMirroringHostResponse hr : mr.Results) {
                if (hr.Status == null || hr.Host == null) {
                    throw new SitaException("Sita mirroring result for host has null fields");
                }

                final SitaMirroringHostStatusEnum hostStatus;
                switch (hr.Status) {
                    case OK:
                        hostStatus = SitaMirroringHostStatusEnum.OK;
                        break;
                    case ERROR:
                        hostStatus = SitaMirroringHostStatusEnum.ERROR;
                        break;
                    case ERROR_HOST_NOT_PROCESSED:
                        hostStatus = SitaMirroringHostStatusEnum.ERROR_HOST_NOT_PROCESSED;
                        break;
                    case ERROR_BAD_HOST_DIRECTIVE_AT_NEW_MAIN:
                        hostStatus = SitaMirroringHostStatusEnum.ERROR_BAD_HOST_DIRECTIVE_AT_NEW_MAIN;
                        break;
                    case ERROR_NOT_UNSTICKED:
                        hostStatus = SitaMirroringHostStatusEnum.ERROR_NOT_UNSTICKED;
                        break;
                    case ERROR_BAD_REAL_MAIN:
                        hostStatus = SitaMirroringHostStatusEnum.ERROR_BAD_REAL_MAIN;
                        break;
                    default:
                        hostStatus = null;
                }

                try {
                    final String host = replaceLangPrefix(hr.Host);
                    final String newMain = replaceLangPrefix(hr.NewMain);
                    final URI newMainURI = newMain != null ? new URI(newMain) : null;
                    hostResults.add(new SitaMirroringResponse.THostResult(hostStatus, new URI(host), newMainURI));
                } catch (URISyntaxException e) {
                    throw new SitaException("Can't parse uri ", e);
                }
            }
        }
        SitaMirroringActionStatusEnum status = null;
        if (mr.Status != null) {
            switch (mr.Status) {
                case OK: status = SitaMirroringActionStatusEnum.OK; break;
                case ERROR_TIMEOUT: status = SitaMirroringActionStatusEnum.ERROR_TIMEOUT; break;
                case ERROR_USER: status = SitaMirroringActionStatusEnum.ERROR_USER; break;
                case ERROR_INTERNAL: status = SitaMirroringActionStatusEnum.ERROR_INTERNAL; break;
                default: status = null;
            }
        }
        return new SitaMirroringResponse(status, hostResults);
    }

    private static String replaceLangPrefix(final String hostName) {
        if (hostName == null) {
            return hostName;
        }

        int atIndex = hostName.indexOf("@");
        if (atIndex < 0) {
            return hostName;
        }
        int schemeDelimiterIndex = hostName.indexOf("://");
        if (atIndex < schemeDelimiterIndex) {
            return hostName.substring(atIndex+1);
        }
        return hostName;
    }
}
