package io.intercom.api;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@SuppressWarnings("UnusedDeclaration")
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class Tag extends TypedData {

    private static final Logger logger = LoggerFactory.getLogger("intercom-java");
    private static final HashMap<String, String> SENTINEL = Maps.newHashMap();

    public static Tag tag(Tag tag, User... users) throws InvalidException, AuthorizationException {
        return tag(tag, new UserCollection(Lists.newArrayList(users)));
    }

    public static Tag tag(Tag tag, Contact... contacts) throws InvalidException, AuthorizationException {
        return tag(tag, new ContactCollection(Lists.newArrayList(contacts)));
    }

    public static Tag tag(Tag tag, Company... companies) throws InvalidException, AuthorizationException {
        return tag(tag, new CompanyCollection(Lists.newArrayList(companies)));
    }

    static Tag tag(Tag tag, UserCollection users) throws InvalidException, AuthorizationException {
        TaggableCollection taggableCollection = createTagTypedCollection(tag, users);
        return DataResource.create(taggableCollection, "tags", Tag.class);
    }

    static Tag tag(Tag tag, ContactCollection contacts) throws InvalidException, AuthorizationException {
        TaggableCollection taggableCollection = createTagTypedCollection(tag, contacts);
        return DataResource.create(taggableCollection, "tags", Tag.class);
    }

    static Tag tag(Tag tag, CompanyCollection companies) throws InvalidException, AuthorizationException {
        TaggableCollection taggableCollection = createTagTypedCollection(tag, companies);
        return DataResource.create(taggableCollection, "tags", Tag.class);
    }

    public static Tag create(Tag tag) throws InvalidException, AuthorizationException {
        return DataResource.create(tag, "tags", Tag.class);
    }

    public static Tag update(Tag tag) throws InvalidException, AuthorizationException {
        if (tag.getId() == null) {
            throw new InvalidException("tag updates must supply an id");
        }
        return DataResource.update(tag, "tags", Tag.class);
    }

    public static Tag delete(String id) throws InvalidException, AuthorizationException {
        return DataResource.delete(id, "tags", Tag.class);
    }

    public static Tag delete(Tag tag) throws InvalidException, AuthorizationException {
        return delete(tag.getId());
    }

    public static TagCollection list() throws InvalidException, AuthorizationException {
        return DataResource.list(SENTINEL, "tags", TagCollection.class);
    }

    @VisibleForTesting
    static TaggableCollection createTagTypedCollection(Tag tag, UserCollection users) {
        TaggableCollection taggableCollection = new TaggableCollection();
        taggableCollection.setName(tag.getName());
        taggableCollection.setId(tag.getId());
        final List<Map<String, Object>> usersLite = Lists.newArrayList();
        final List<User> pageItems = users.getPage();
        for (User user : pageItems) {
            Map<String, Object> userMap = Maps.newHashMap();
            final String id = user.getId();
            final String email = user.getEmail();
            final String userId = user.getUserId();

            if (user.isUntag()) {
                userMap.put("untag", true);
            }

            if (!Strings.isNullOrEmpty(id)) {
                userMap.put("id", id);
                usersLite.add(userMap);
            } else if (!Strings.isNullOrEmpty(userId)) {
                userMap.put("user_id", userId);
                usersLite.add(userMap);
            } else if (!Strings.isNullOrEmpty(email)) {
                userMap.put("email", email);
                usersLite.add(userMap);
            } else {
                logger.warn("no identifiers found for user tag target, skipping [" + tag + "] [" + user.toString() + "]");
            }
        }
        taggableCollection.setUsers(usersLite);
        return taggableCollection;
    }

    @VisibleForTesting
    static TaggableCollection createTagTypedCollection(Tag tag, ContactCollection contacts) {
        TaggableCollection taggableCollection = new TaggableCollection();
        taggableCollection.setName(tag.getName());
        taggableCollection.setId(tag.getId());
        final List<Map<String, Object>> contactsLite = Lists.newArrayList();
        final List<Contact> pageItems = contacts.getPage();
        for (Contact contact: pageItems) {
            Map<String, Object> contactMap = Maps.newHashMap();
            final String id = contact.getID();

            if (contact.isUntag()) {
                contactMap.put("untag", true);
            }

            if (!Strings.isNullOrEmpty(id)) {
                contactMap.put("id", id);
                contactsLite.add(contactMap);
            } else {
                logger.warn("no identifiers found for user tag target, skipping [" + tag + "] [" + contact.toString() + "]");
            }
        }
        taggableCollection.setUsers(contactsLite);
        return taggableCollection;
    }

    @VisibleForTesting
    static TaggableCollection createTagTypedCollection(Tag tag, CompanyCollection companies) {
        TaggableCollection taggableCollection = new TaggableCollection();
        taggableCollection.setName(tag.getName());
        taggableCollection.setId(tag.getId());

        final List<Map<String, Object>> companiesLite = Lists.newArrayList();
        final List<Company> pageItems = companies.getPage();
        for (Company company : pageItems) {
            Map<String, Object> companyMap = Maps.newHashMap();
            final String companyID = company.getCompanyID();
            final String id = company.getId();
            final String name = company.getName();

            if (company.isUntag()) {
                companyMap.put("untag", true);
            }

            if (!Strings.isNullOrEmpty(companyID)) {
                companyMap.put("company_id", companyID);
                companiesLite.add(companyMap);
            } else if (!Strings.isNullOrEmpty(id)) {
                companyMap.put("id", id);
                companiesLite.add(companyMap);
            } else if (!Strings.isNullOrEmpty(name)) {
                companyMap.put("name", name);
                companiesLite.add(companyMap);
            } else {
                logger.warn("no identifiers found for company tag target, skipping [" + tag + "] [" + company.toString() + "]");
            }
            taggableCollection.setCompanies(companiesLite);
        }
        return taggableCollection;
    }

    @SuppressWarnings("UnusedDeclaration")
    @JsonIgnoreProperties(ignoreUnknown = true)
    @JsonInclude(JsonInclude.Include.NON_DEFAULT)
    static class TaggableCollection extends Tag {

        @JsonProperty("users")
        private List<Map<String, Object>> users;

        @JsonProperty("companies")
        private List<Map<String, Object>> companies;

        public List<Map<String, Object>> getUsers() {
            return users;
        }

        public void setUsers(List<Map<String, Object>> usersLite) {
            this.users = usersLite;
        }

        public List<Map<String, Object>> getCompanies() {
            return companies;
        }

        public void setCompanies(List<Map<String, Object>> companies) {
            this.companies = companies;
        }
    }

    @JsonProperty("type")
    private final String type = "tag";

    @JsonProperty("id")
    private String id;

    @JsonProperty("name")
    private String name;

    @JsonProperty("created_at")
    private long createdAt;

    @JsonProperty("updated_at")
    private long updatedAt;

    public Tag() {
    }

    public String getType() {
        return type;
    }

    public String getId() {
        return id;
    }


    @SuppressWarnings("UnusedReturnValue")
    @VisibleForTesting
    Tag setId(String id) {
        this.id = id;
        return this;
    }

    public String getName() {
        return name;
    }

    public Tag setName(String name) {
        this.name = name;
        return this;
    }

    public long getCreatedAt() {
        return createdAt;
    }

    public long getUpdatedAt() {
        return updatedAt;
    }

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

        Tag tag = (Tag) o;

        if (createdAt != tag.createdAt) return false;
        if (updatedAt != tag.updatedAt) return false;
        if (id != null ? !id.equals(tag.id) : tag.id != null) return false;
        if (name != null ? !name.equals(tag.name) : tag.name != null) return false;
        //noinspection RedundantIfStatement
        if (!type.equals(tag.type)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = type.hashCode();
        result = 31 * result + (id != null ? id.hashCode() : 0);
        result = 31 * result + (name != null ? name.hashCode() : 0);
        result = 31 * result + (int) (createdAt ^ (createdAt >>> 32));
        result = 31 * result + (int) (updatedAt ^ (updatedAt >>> 32));
        return result;
    }

    @Override
    public String toString() {
        return "Tag{" +
            "id='" + id + '\'' +
            ", name='" + name + '\'' +
            ", createdAt=" + createdAt +
            ", updatedAt=" + updatedAt +
            "} " + super.toString();
    }
}