/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License;
 * you may not use this file except in compliance with the Elastic License.
 */
package org.elasticsearch.xpack.notification.pagerduty;

import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.xpack.common.http.HttpRequest;
import org.elasticsearch.xpack.common.http.HttpResponse;
import org.elasticsearch.xpack.watcher.actions.pagerduty.PagerDutyAction;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class SentEvent implements ToXContentObject {

    final IncidentEvent event;
    @Nullable final HttpRequest request;
    @Nullable final HttpResponse response;
    @Nullable final String failureReason;

    public static SentEvent responded(IncidentEvent event, HttpRequest request, HttpResponse response) {
        String failureReason = resolveFailureReason(response);
        return new SentEvent(event, request, response, failureReason);
    }

    public static SentEvent error(IncidentEvent event, String reason) {
        return new SentEvent(event, null, null, reason);
    }

    private SentEvent(IncidentEvent event, HttpRequest request, HttpResponse response, String failureReason) {
        this.event = event;
        this.request = request;
        this.response = response;
        this.failureReason = failureReason;
    }

    public boolean successful() {
        return failureReason == null;
    }

    public HttpRequest getRequest() {
        return request;
    }

    public HttpResponse getResponse() {
        return response;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        SentEvent sentEvent = (SentEvent) o;
        return Objects.equals(event, sentEvent.event) &&
                Objects.equals(request, sentEvent.request) &&
                Objects.equals(failureReason, sentEvent.failureReason);
    }

    @Override
    public int hashCode() {
        return Objects.hash(event, request, response, failureReason);
    }

    @Override
    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
        builder.startObject();
        builder.field(XField.EVENT.getPreferredName(), event, params);
        if (!successful()) {
            builder.field(XField.REASON.getPreferredName(), failureReason);
            if (request != null) {
                builder.field(XField.REQUEST.getPreferredName(), request, params);
            }
            if (response != null) {
                builder.field(XField.RESPONSE.getPreferredName(), response, params);
            }
        }
        return builder.endObject();
    }

    private static String resolveFailureReason(HttpResponse response) {

        // if for some reason we failed to parse the body, lets fall back on the http status code.
        int status = response.status();
        if (status < 300) {
            return null;
        }

        // ok... we have an error

        // lets first try to parse the error response in the body
        // based on https://developer.pagerduty.com/documentation/rest/errors
        try {
            // EMPTY is safe here because we never call namedObject
            XContentParser parser = JsonXContent.jsonXContent.createParser(NamedXContentRegistry.EMPTY, response.body());
            parser.nextToken();

            String message = null;
            List<String> errors = new ArrayList<>();

            String currentFieldName = null;
            XContentParser.Token token;
            while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
                if (token == XContentParser.Token.FIELD_NAME) {
                    currentFieldName = parser.currentName();
                } else if (XField.MESSAGE.match(currentFieldName)) {
                    message = parser.text();
                } else if (XField.CODE.match(currentFieldName)) {
                    // we don't use this code.. so just consume the token
                } else if (XField.ERRORS.match(currentFieldName)) {
                    while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
                        errors.add(parser.text());
                    }
                } else {
                    throw new ElasticsearchParseException("could not parse pagerduty event response. unexpected field [{}]",
                            currentFieldName);
                }
            }

            StringBuilder sb = new StringBuilder();
            if (message != null) {
                sb.append(message);
            }
            if (!errors.isEmpty()) {
                sb.append(":");
                for (String error : errors) {
                    sb.append(" ").append(error).append(".");
                }
            }
            return sb.toString();
        } catch (Exception e) {
            // too bad... we couldn't parse the body... note that we don't log this error as there's no real
            // need for it. This whole error parsing is a nice to have, nothing more. On error, the http
            // response object is anyway added to the action result in the watch record (though not searchable)
        }

        switch (status) {
            case 400:   return "Bad Request";
            case 401:   return "Unauthorized. The account service api key is invalid.";
            case 403:   return "Forbidden. The account doesn't have permission to send this trigger.";
            case 404:   return "The account used invalid HipChat APIs";
            case 408:   return "Request Timeout. The request took too long to process.";
            case 500:   return "PagerDuty Server Error. Internal error occurred while processing request.";
            default:
                return "Unknown Error";
        }
    }

    public interface XField {
        ParseField EVENT = PagerDutyAction.XField.EVENT;
        ParseField REASON = new ParseField("reason");
        ParseField REQUEST = new ParseField("request");
        ParseField RESPONSE = new ParseField("response");

        ParseField MESSAGE = new ParseField("message");
        ParseField CODE = new ParseField("code");
        ParseField ERRORS = new ParseField("errors");
    }
}
