/*
 * 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.slack.message;

import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.xpack.common.text.TextTemplate;
import org.elasticsearch.xpack.common.text.TextTemplateEngine;

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

public class Attachment implements MessageElement {

    final String fallback;
    final String color;
    final String pretext;
    final String authorName;
    final String authorLink;
    final String authorIcon;
    final String title;
    final String titleLink;
    final String text;
    final Field[] fields;
    final String imageUrl;
    final String thumbUrl;
    final String[] markdownSupportedFields;

    public Attachment(String fallback, String color, String pretext, String authorName, String authorLink,
                      String authorIcon, String title, String titleLink, String text, Field[] fields,
                      String imageUrl, String thumbUrl, String[] markdownSupportedFields) {

        this.fallback = fallback;
        this.color = color;
        this.pretext = pretext;
        this.authorName = authorName;
        this.authorLink = authorLink;
        this.authorIcon = authorIcon;
        this.title = title;
        this.titleLink = titleLink;
        this.text = text;
        this.fields = fields;
        this.imageUrl = imageUrl;
        this.thumbUrl = thumbUrl;
        this.markdownSupportedFields = markdownSupportedFields;
    }

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

        Attachment that = (Attachment) o;
        return Objects.equals(fallback, that.fallback) && Objects.equals(color, that.color) &&
                Objects.equals(pretext, that.pretext) && Objects.equals(authorName, that.authorName) &&
                Objects.equals(authorLink, that.authorLink) && Objects.equals(authorIcon, that.authorIcon) &&
                Objects.equals(title, that.title) && Objects.equals(titleLink, that.titleLink) &&
                Objects.equals(text, that.text) && Objects.equals(imageUrl, that.imageUrl) &&
                Objects.equals(thumbUrl, that.thumbUrl) &&
                Arrays.equals(markdownSupportedFields, that.markdownSupportedFields) && Arrays.equals(fields, that.fields);
    }

    @Override
    public int hashCode() {
        return Objects.hash(fallback, color, pretext, authorName, authorLink, authorIcon, title, titleLink, text, fields, imageUrl,
                thumbUrl, markdownSupportedFields);
    }

    /**
     * renders the attachment in slack compatible structure:
     * <p>
     * https://api.slack.com/docs/attachments#attachment_structure
     */
    @Override
    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
        builder.startObject();
        if (fallback != null) {
            builder.field(XField.FALLBACK.getPreferredName(), fallback);
        }
        if (color != null) {
            builder.field(XField.COLOR.getPreferredName(), color);
        }
        if (pretext != null) {
            builder.field(XField.PRETEXT.getPreferredName(), pretext);
        }
        if (authorName != null) {
            builder.field(XField.AUTHOR_NAME.getPreferredName(), authorName);
            if (authorLink != null) {
                builder.field(XField.AUTHOR_LINK.getPreferredName(), authorLink);
            }
            if (authorIcon != null) {
                builder.field(XField.AUTHOR_ICON.getPreferredName(), authorIcon);
            }
        }
        if (title != null) {
            builder.field(XField.TITLE.getPreferredName(), title);
            if (titleLink != null) {
                builder.field(XField.TITLE_LINK.getPreferredName(), titleLink);
            }
        }
        if (text != null) {
            builder.field(XField.TEXT.getPreferredName(), text);
        }
        if (fields != null) {
            builder.startArray(XField.FIELDS.getPreferredName());
            for (Field field : fields) {
                field.toXContent(builder, params);
            }
            builder.endArray();
        }
        if (imageUrl != null) {
            builder.field(XField.IMAGE_URL.getPreferredName(), imageUrl);
        }
        if (thumbUrl != null) {
            builder.field(XField.THUMB_URL.getPreferredName(), thumbUrl);
        }
        if (markdownSupportedFields != null) {
            builder.startArray(XField.MARKDOWN_IN.getPreferredName());
            for (String field : markdownSupportedFields) {
                builder.value(field);
            }
            builder.endArray();
        }

        return builder.endObject();
    }

    static class Template implements ToXContentObject {

        final TextTemplate fallback;
        final TextTemplate color;
        final TextTemplate pretext;
        final TextTemplate authorName;
        final TextTemplate authorLink;
        final TextTemplate authorIcon;
        final TextTemplate title;
        final TextTemplate titleLink;
        final TextTemplate text;
        final Field.Template[] fields;
        final TextTemplate imageUrl;
        final TextTemplate thumbUrl;
        final TextTemplate[] markdownSupportedFields;


        Template(TextTemplate fallback, TextTemplate color, TextTemplate pretext, TextTemplate authorName,
                 TextTemplate authorLink, TextTemplate authorIcon, TextTemplate title, TextTemplate titleLink,
                 TextTemplate text, Field.Template[] fields, TextTemplate imageUrl, TextTemplate thumbUrl,
                 TextTemplate[] markdownSupportedFields) {

            this.fallback = fallback;
            this.color = color;
            this.pretext = pretext;
            this.authorName = authorName;
            this.authorLink = authorLink;
            this.authorIcon = authorIcon;
            this.title = title;
            this.titleLink = titleLink;
            this.text = text;
            this.fields = fields;
            this.imageUrl = imageUrl;
            this.thumbUrl = thumbUrl;
            this.markdownSupportedFields = markdownSupportedFields;
        }

        public Attachment render(TextTemplateEngine engine, Map<String, Object> model, SlackMessageDefaults.AttachmentDefaults defaults) {
            String fallback = this.fallback != null ? engine.render(this.fallback, model) : defaults.fallback;
            String color = this.color != null ? engine.render(this.color, model) : defaults.color;
            String pretext = this.pretext != null ? engine.render(this.pretext, model) : defaults.pretext;
            String authorName = this.authorName != null ? engine.render(this.authorName, model) : defaults.authorName;
            String authorLink = this.authorLink != null ? engine.render(this.authorLink, model) : defaults.authorLink;
            String authorIcon = this.authorIcon != null ? engine.render(this.authorIcon, model) : defaults.authorIcon;
            String title = this.title != null ? engine.render(this.title, model) : defaults.title;
            String titleLink = this.titleLink != null ? engine.render(this.titleLink, model) : defaults.titleLink;
            String text = this.text != null ? engine.render(this.text, model) : defaults.text;
            String imageUrl = this.imageUrl != null ? engine.render(this.imageUrl, model) : defaults.imageUrl;
            String thumbUrl = this.thumbUrl != null ? engine.render(this.thumbUrl, model) : defaults.thumbUrl;
            Field[] fields = null;
            if (this.fields != null) {
                fields = new Field[this.fields.length];
                for (int i = 0; i < fields.length; i++) {
                    fields[i] = this.fields[i].render(engine, model, defaults.field);
                }
            }
            String[] markdownFields = null;
            if (this.markdownSupportedFields != null) {
                markdownFields = new String[this.markdownSupportedFields.length];
                for (int i = 0; i < markdownSupportedFields.length; i++) {
                    markdownFields[i] = engine.render(this.markdownSupportedFields[i], model);
                }
            }
            return new Attachment(fallback, color, pretext, authorName, authorLink, authorIcon, title, titleLink, text, fields, imageUrl,
                    thumbUrl, markdownFields);
        }

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

            Template template = (Template) o;

            return Objects.equals(fallback, template.fallback) && Objects.equals(color, template.color)
                    && Objects.equals(pretext, template.pretext) && Objects.equals(authorName, template.authorName)
                    && Objects.equals(authorLink, template.authorLink) && Objects.equals(authorIcon, template.authorIcon)
                    && Objects.equals(title, template.title) && Objects.equals(titleLink, template.titleLink)
                    && Objects.equals(text, template.text) && Objects.equals(imageUrl, template.imageUrl)
                    && Objects.equals(thumbUrl, template.thumbUrl)
                    && Arrays.equals(fields, template.fields)
                    && Arrays.equals(markdownSupportedFields, template.markdownSupportedFields);
        }

        @Override
        public int hashCode() {
            return Objects.hash(fallback, color, pretext, authorName, authorLink, authorIcon, title, titleLink, text, fields, imageUrl,
                    thumbUrl, markdownSupportedFields);
        }

        @Override
        public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
            builder.startObject();
            if (fallback != null) {
                builder.field(XField.FALLBACK.getPreferredName(), fallback, params);
            }
            if (color != null) {
                builder.field(XField.COLOR.getPreferredName(), color, params);
            }
            if (pretext != null) {
                builder.field(XField.PRETEXT.getPreferredName(), pretext, params);
            }
            if (authorName != null) {
                builder.field(XField.AUTHOR_NAME.getPreferredName(), authorName, params);
                if (authorLink != null) {
                    builder.field(XField.AUTHOR_LINK.getPreferredName(), authorLink, params);
                }
                if (authorIcon != null) {
                    builder.field(XField.AUTHOR_ICON.getPreferredName(), authorIcon, params);
                }
            }
            if (title != null) {
                builder.field(XField.TITLE.getPreferredName(), title, params);
                if (titleLink != null) {
                    builder.field(XField.TITLE_LINK.getPreferredName(), titleLink, params);
                }
            }
            if (text != null) {
                builder.field(XField.TEXT.getPreferredName(), text, params);
            }
            if (fields != null) {
                builder.startArray(XField.FIELDS.getPreferredName());
                for (Field.Template field : fields) {
                    field.toXContent(builder, params);
                }
                builder.endArray();
            }
            if (imageUrl != null) {
                builder.field(XField.IMAGE_URL.getPreferredName(), imageUrl, params);
            }
            if (thumbUrl != null) {
                builder.field(XField.THUMB_URL.getPreferredName(), thumbUrl, params);
            }
            if (markdownSupportedFields != null) {
                builder.startArray(XField.MARKDOWN_IN.getPreferredName());
                for (TextTemplate field : markdownSupportedFields) {
                    field.toXContent(builder, params);
                }
                builder.endArray();
            }

            return builder.endObject();
        }

        public static Template parse(XContentParser parser) throws IOException {

            TextTemplate fallback = null;
            TextTemplate color = null;
            TextTemplate pretext = null;
            TextTemplate authorName = null;
            TextTemplate authorLink = null;
            TextTemplate authorIcon = null;
            TextTemplate title = null;
            TextTemplate titleLink = null;
            TextTemplate text = null;
            Field.Template[] fields = null;
            TextTemplate imageUrl = null;
            TextTemplate thumbUrl = null;
            TextTemplate[] markdownFields = null;

            XContentParser.Token token = null;
            String currentFieldName = null;
            while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
                if (token == XContentParser.Token.FIELD_NAME) {
                    currentFieldName = parser.currentName();
                } else if (XField.FALLBACK.match(currentFieldName)) {
                    try {
                        fallback = TextTemplate.parse(parser);
                    } catch (ElasticsearchParseException pe) {
                        throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field", pe,
                                XField.FALLBACK);
                    }
                } else if (XField.COLOR.match(currentFieldName)) {
                    try {
                        color = TextTemplate.parse(parser);
                    } catch (ElasticsearchParseException pe) {
                        throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field", pe,
                                XField.COLOR);
                    }
                } else if (XField.PRETEXT.match(currentFieldName)) {
                    try {
                        pretext = TextTemplate.parse(parser);
                    } catch (ElasticsearchParseException pe) {
                        throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field", pe,
                                XField.PRETEXT);
                    }
                } else if (XField.AUTHOR_NAME.match(currentFieldName)) {
                    try {
                        authorName = TextTemplate.parse(parser);
                    } catch (ElasticsearchParseException pe) {
                        throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field", pe,
                                XField.AUTHOR_NAME);
                    }
                } else if (XField.AUTHOR_LINK.match(currentFieldName)) {
                    try {
                        authorLink = TextTemplate.parse(parser);
                    } catch (ElasticsearchParseException pe) {
                        throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field", pe,
                                XField.AUTHOR_LINK);
                    }
                } else if (XField.AUTHOR_ICON.match(currentFieldName)) {
                    try {
                        authorIcon = TextTemplate.parse(parser);
                    } catch (ElasticsearchParseException pe) {
                        throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field", pe,
                                XField.AUTHOR_ICON);
                    }
                } else if (XField.TITLE.match(currentFieldName)) {
                    try {
                        title = TextTemplate.parse(parser);
                    } catch (ElasticsearchParseException pe) {
                        throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field", pe,
                                XField.TITLE);
                    }
                } else if (XField.TITLE_LINK.match(currentFieldName)) {
                    try {
                        titleLink = TextTemplate.parse(parser);
                    } catch (ElasticsearchParseException pe) {
                        throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field", pe,
                                XField.TITLE_LINK);
                    }
                } else if (XField.TEXT.match(currentFieldName)) {
                    try {
                        text = TextTemplate.parse(parser);
                    } catch (ElasticsearchParseException pe) {
                        throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field", pe,
                                XField.TEXT);
                    }
                } else if (XField.FIELDS.match(currentFieldName)) {
                    if (token == XContentParser.Token.START_ARRAY) {
                        List<Field.Template> list = new ArrayList<>();
                        while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
                            try {
                                list.add(Field.Template.parse(parser));
                            } catch (ElasticsearchParseException pe) {
                                throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field",
                                        pe, XField.FIELDS);
                            }
                        }
                        fields = list.toArray(new Field.Template[list.size()]);
                    } else {
                        try {
                            fields = new Field.Template[]{Field.Template.parse(parser)};
                        } catch (ElasticsearchParseException pe) {
                            throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field", pe,
                                    XField.FIELDS);
                        }
                    }
                } else if (XField.IMAGE_URL.match(currentFieldName)) {
                    try {
                        imageUrl = TextTemplate.parse(parser);
                    } catch (ElasticsearchParseException pe) {
                        throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field", pe,
                                XField.IMAGE_URL);
                    }
                } else if (XField.THUMB_URL.match(currentFieldName)) {
                    try {
                        thumbUrl = TextTemplate.parse(parser);
                    } catch (ElasticsearchParseException pe) {
                        throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field", pe,
                                XField.THUMB_URL);
                    }
                } else if (XField.MARKDOWN_IN.match(currentFieldName)) {
                    if (token == XContentParser.Token.START_ARRAY) {
                        List<TextTemplate> list = new ArrayList<>();
                        while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
                            try {
                                list.add(new TextTemplate(parser.text()));
                            } catch (ElasticsearchParseException pe) {
                                throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field",
                                        pe, XField.MARKDOWN_IN);
                            }
                        }
                        markdownFields = list.toArray(new TextTemplate[list.size()]);
                    } else {
                        try {
                            markdownFields = new TextTemplate[]{ new TextTemplate(parser.text())};
                        } catch (ElasticsearchParseException pe) {
                            throw new ElasticsearchParseException("could not parse message attachment. failed to parse [{}] field", pe,
                                    XField.MARKDOWN_IN);
                        }
                    }
                } else {
                    throw new ElasticsearchParseException("could not parse message attachment field. unexpected field [{}]",
                            currentFieldName);
                }
            }
            if (authorName == null) {
                if (authorLink != null) {
                    throw new ElasticsearchParseException("could not parse message attachment field. found field [{}], but no [{}] is " +
                            "defined", XField.AUTHOR_LINK, XField.AUTHOR_NAME);
                }
                if (authorIcon != null) {
                    throw new ElasticsearchParseException("could not parse message attachment field. found field [{}], but no [{}] is " +
                            "defined", XField.AUTHOR_ICON, XField.AUTHOR_NAME);
                }
            }
            if (title == null) {
                if (titleLink != null) {
                    throw new ElasticsearchParseException("could not parse message attachment field. found field [{}], but no [{}] is " +
                            "defined", XField.TITLE_LINK, XField.TITLE);
                }
            }
            return new Template(fallback, color, pretext, authorName, authorLink, authorIcon, title, titleLink, text, fields, imageUrl,
                    thumbUrl, markdownFields);
        }


        public static Builder builder() {
            return new Builder();
        }

        public static class Builder {

            private TextTemplate fallback;
            private TextTemplate color;
            private TextTemplate pretext;
            private TextTemplate authorName;
            private TextTemplate authorLink;
            private TextTemplate authorIcon;
            private TextTemplate title;
            private TextTemplate titleLink;
            private TextTemplate text;
            private List<Field.Template> fields = new ArrayList<>();
            private TextTemplate imageUrl;
            private TextTemplate thumbUrl;
            private List<TextTemplate> markdownFields = new ArrayList<>();

            private Builder() {
            }

            public Builder setFallback(TextTemplate fallback) {
                this.fallback = fallback;
                return this;
            }

            public Builder setFallback(String fallback) {
                return setFallback(new TextTemplate(fallback));
            }

            public Builder setColor(TextTemplate color) {
                this.color = color;
                return this;
            }

            public Builder setColor(String color) {
                return setColor(new TextTemplate(color));
            }

            public Builder setPretext(TextTemplate pretext) {
                this.pretext = pretext;
                return this;
            }

            public Builder setPretext(String pretext) {
                return setPretext(new TextTemplate(pretext));
            }

            public Builder setAuthorName(TextTemplate authorName) {
                this.authorName = authorName;
                return this;
            }

            public Builder setAuthorName(String authorName) {
                return setAuthorName(new TextTemplate(authorName));
            }

            public Builder setAuthorLink(TextTemplate authorLink) {
                this.authorLink = authorLink;
                return this;
            }

            public Builder setAuthorLink(String authorLink) {
                return setAuthorLink(new TextTemplate(authorLink));
            }

            public Builder setAuthorIcon(TextTemplate authorIcon) {
                this.authorIcon = authorIcon;
                return this;
            }

            public Builder setAuthorIcon(String authorIcon) {
                return setAuthorIcon(new TextTemplate(authorIcon));
            }

            public Builder setTitle(TextTemplate title) {
                this.title = title;
                return this;
            }

            public Builder setTitle(String title) {
                return setTitle(new TextTemplate(title));
            }

            public Builder setTitleLink(TextTemplate titleLink) {
                this.titleLink = titleLink;
                return this;
            }

            public Builder setTitleLink(String titleLink) {
                return setTitleLink(new TextTemplate(titleLink));
            }

            public Builder setText(TextTemplate text) {
                this.text = text;
                return this;
            }

            public Builder setText(String text) {
                return setText(new TextTemplate(text));
            }

            public Builder addField(TextTemplate title, TextTemplate value, boolean isShort) {
                fields.add(new Field.Template(title, value, isShort));
                return this;
            }

            public Builder addField(String title, String value, boolean isShort) {
                return addField(new TextTemplate(title), new TextTemplate(value), isShort);
            }

            public Builder setImageUrl(TextTemplate imageUrl) {
                this.imageUrl = imageUrl;
                return this;
            }

            public Builder setImageUrl(String imageUrl) {
                return setImageUrl(new TextTemplate(imageUrl));
            }

            public Builder setThumbUrl(TextTemplate thumbUrl) {
                this.thumbUrl = thumbUrl;
                return this;
            }

            public Builder setThumbUrl(String thumbUrl) {
                return setThumbUrl(new TextTemplate(thumbUrl));
            }

            public Builder addMarkdownField(String name) {
                this.markdownFields.add(new TextTemplate(name));
                return this;
            }

            public Template build() {
                Field.Template[] fields = this.fields.isEmpty() ? null : this.fields.toArray(new Field.Template[this.fields.size()]);
                TextTemplate[] markdownFields =
                        this.markdownFields.isEmpty() ? null : this.markdownFields.toArray(new TextTemplate[this.markdownFields.size()]);
                return new Template(fallback, color, pretext, authorName, authorLink, authorIcon, title, titleLink, text, fields,
                        imageUrl, thumbUrl, markdownFields);
            }
        }
    }

    interface XField extends MessageElement.XField {
        ParseField FALLBACK = new ParseField("fallback");
        ParseField COLOR = new ParseField("color");
        ParseField PRETEXT = new ParseField("pretext");
        ParseField AUTHOR_NAME = new ParseField("author_name");
        ParseField AUTHOR_LINK = new ParseField("author_link");
        ParseField AUTHOR_ICON = new ParseField("author_icon");

        ParseField TITLE_LINK = new ParseField("title_link");
        ParseField FIELDS = new ParseField("fields");
        ParseField IMAGE_URL = new ParseField("image_url");
        ParseField THUMB_URL = new ParseField("thumb_url");

        ParseField MARKDOWN_IN = new ParseField("mrkdwn_in");
    }
}
