package com.box.sdk;

import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;
import com.eclipsesource.json.JsonValue;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Represents a retention policy assignment.
 *
 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked
 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error
 * handling for errors related to the Box REST API, you should capture this exception explicitly.</p>
 */
@BoxResourceType("retention_policy_assignment")
public class BoxRetentionPolicyAssignment extends BoxResource {

    /**
     * Type for folder policy assignment.
     */
    public static final String TYPE_FOLDER = "folder";

    /**
     * Type for enterprise policy assignment.
     */
    public static final String TYPE_ENTERPRISE = "enterprise";

    /**
     * Type for metadata policy assignment.
     */
    public static final String TYPE_METADATA = "metadata_template";

    /**
     * The URL template used for operation with retention policy assignments.
     */
    public static final URLTemplate ASSIGNMENTS_URL_TEMPLATE = new URLTemplate("retention_policy_assignments");

    /**
     * The URL template used for operation with retention policy assignment with given ID.
     */
    public static final URLTemplate RETENTION_POLICY_ASSIGNMENT_URL_TEMPLATE
        = new URLTemplate("retention_policy_assignments/%s");

    /**
     * The URL template used for operation with files under retention with given retention policy assignment ID.
     */
    public static final URLTemplate FILES_UNDER_RETENTION_URL_TEMPLATE
        = new URLTemplate("retention_policy_assignments/%s/files_under_retention");

    /**
     * The URL template used for operation with file versions under retention with given retention policy assignment ID.
     */
    public static final URLTemplate FILE_VERSIONS_UNDER_RETENTION_URL_TEMPLATE
        = new URLTemplate("retention_policy_assignments/%s/file_versions_under_retention");

    private static final int DEFAULT_LIMIT = 100;

    /**
     * Constructs a BoxResource for a resource with a given ID.
     *
     * @param api the API connection to be used by the resource.
     * @param id  the ID of the resource.
     */
    public BoxRetentionPolicyAssignment(BoxAPIConnection api, String id) {
        super(api, id);
    }

    /**
     * Assigns retention policy with givenID to the enterprise.
     *
     * @param api      the API connection to be used by the created assignment.
     * @param policyID id of the assigned retention policy.
     * @return info about created assignment.
     */
    public static BoxRetentionPolicyAssignment.Info createAssignmentToEnterprise(BoxAPIConnection api,
                                                                                 String policyID) {
        return createAssignment(api, policyID, new JsonObject().add("type", TYPE_ENTERPRISE), null, null);
    }

    /**
     * Assigns retention policy with givenID to the folder.
     *
     * @param api      the API connection to be used by the created assignment.
     * @param policyID id of the assigned retention policy.
     * @param folderID id of the folder to assign policy to.
     * @return info about created assignment.
     */
    public static BoxRetentionPolicyAssignment.Info createAssignmentToFolder(BoxAPIConnection api, String policyID,
                                                                             String folderID) {
        return createAssignment(
                api, policyID, new JsonObject().add("type", TYPE_FOLDER).add("id", folderID), null, null);
    }

    /**
     * Assigns a retention policy to all items with a given metadata template, optionally matching on fields.
     *
     * @param api        the API connection to be used by the created assignment.
     * @param policyID   id of the assigned retention policy.
     * @param templateID the ID of the metadata template to assign the policy to.
     * @param filter     optional fields to match against in the metadata template.
     * @return info about the created assignment.
     */
    public static BoxRetentionPolicyAssignment.Info createAssignmentToMetadata(BoxAPIConnection api,
                                                                               String policyID,
                                                                               String templateID,
                                                                               MetadataFieldFilter... filter) {
        return createAssignmentToMetadata(api, policyID, templateID, null, filter);
    }

    /**
     * Assigns a retention policy to all items with a given metadata template, optionally matching on fields.
     *
     * @param api        the API connection to be used by the created assignment.
     * @param policyID   id of the assigned retention policy.
     * @param templateID the ID of the metadata template to assign the policy to.
     * @param startDateField  The date the retention policy assignment begins. This field can be a date field's metadata attribute key id.
     * @param filter     optional fields to match against in the metadata template.
     * @return info about the created assignment.
     */
    public static BoxRetentionPolicyAssignment.Info createAssignmentToMetadata(BoxAPIConnection api,
                                                                               String policyID,
                                                                               String templateID,
                                                                               String startDateField,
                                                                               MetadataFieldFilter... filter) {
        JsonObject assignTo = new JsonObject().add("type", TYPE_METADATA).add("id", templateID);
        JsonArray filters = null;
        if (filter.length > 0) {
            filters = new JsonArray();
            for (MetadataFieldFilter f : filter) {
                filters.add(f.getJsonObject());
            }
        }
        return createAssignment(api, policyID, assignTo, startDateField, filters);
    }

    /**
     * Assigns retention policy with givenID to folder or enterprise.
     *
     * @param api      the API connection to be used by the created assignment.
     * @param policyID id of the assigned retention policy.
     * @param assignTo object representing folder or enterprise to assign policy to.
     * @param filter Filters
     * @return info about created assignment.
     */
    private static BoxRetentionPolicyAssignment.Info createAssignment(BoxAPIConnection api,
                                                                      String policyID,
                                                                      JsonObject assignTo,
                                                                      String startDateField,
                                                                      JsonArray filter) {
        URL url = ASSIGNMENTS_URL_TEMPLATE.build(api.getBaseURL());
        BoxJSONRequest request = new BoxJSONRequest(api, url, "POST");

        JsonObject requestJSON = new JsonObject()
            .add("policy_id", policyID)
            .add("assign_to", assignTo);

        if (filter != null) {
            requestJSON.add("filter_fields", filter);
        }
        if (startDateField != null) {
            requestJSON.add("start_date_field", startDateField);
        }

        request.setBody(requestJSON.toString());
        try (BoxJSONResponse response = request.send()) {
            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
            BoxRetentionPolicyAssignment createdAssignment
                = new BoxRetentionPolicyAssignment(api, responseJSON.get("id").asString());
            return createdAssignment.new Info(responseJSON);
        }
    }

    /**
     * @param fields the fields to retrieve.
     * @return information about this retention policy assignment.
     */
    public BoxRetentionPolicyAssignment.Info getInfo(String... fields) {
        QueryStringBuilder builder = new QueryStringBuilder();
        if (fields.length > 0) {
            builder.appendParam("fields", fields);
        }
        URL url = RETENTION_POLICY_ASSIGNMENT_URL_TEMPLATE.buildWithQuery(
            this.getAPI().getBaseURL(), builder.toString(), this.getID());
        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
        try (BoxJSONResponse response = request.send()) {
            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
            return new Info(responseJSON);
        }
    }

    /**
     * Retrieves all files under retention for assignment as Iterable. Default limit is 100
     *
     * @param fields the fields to retrieve.
     * @return an iterable contains information about all files under retentions as Iterable.
     */
    public Iterable<BoxFile.Info> getFilesUnderRetention(String... fields) {
        return this.getFilesUnderRetention(DEFAULT_LIMIT, fields);
    }

    /**
     * Retrieves all files under retention for assignment as Iterable.
     *
     * @param limit  the limit of retrieved entries per page.
     * @param fields the fields to retrieve.
     * @return an iterable contains information about all files under retentions as Iterable.
     */
    public Iterable<BoxFile.Info> getFilesUnderRetention(int limit, String... fields) {
        QueryStringBuilder queryString = new QueryStringBuilder();
        if (fields.length > 0) {
            queryString.appendParam("fields", fields);
        }
        URL url = FILES_UNDER_RETENTION_URL_TEMPLATE.buildWithQuery(getAPI().getBaseURL(),
            queryString.toString(), getID());
        return new BoxResourceIterable<BoxFile.Info>(getAPI(), url, limit) {
            @Override
            protected BoxFile.Info factory(JsonObject jsonObject) {
                BoxFile boxFile = new BoxFile(getAPI(), jsonObject.get("id").asString());
                return boxFile.new Info(jsonObject);
            }
        };
    }

    /**
     * Retrieves all file version under retention for assignment as Iterable. Default limit is 100.
     *
     * @param fields the fields to retrieve.
     * @return an iterable contains information about all file versions under retentions as Iterable.
     */
    public Iterable<BoxFile.Info> getFileVersionsUnderRetention(String... fields) {
        return this.getFileVersionsUnderRetention(DEFAULT_LIMIT, fields);
    }

    /**
     * Retrieves all file version under retention for assignment as Iterable.
     *
     * @param limit  the limit of retrieved entries per page.
     * @param fields the fields to retrieve.
     * @return an iterable contains information about all file versions under retentions as Iterable.
     */
    public Iterable<BoxFile.Info> getFileVersionsUnderRetention(int limit, String... fields) {
        QueryStringBuilder queryString = new QueryStringBuilder();
        if (fields.length > 0) {
            queryString.appendParam("fields", fields);
        }
        URL url = FILE_VERSIONS_UNDER_RETENTION_URL_TEMPLATE.buildWithQuery(getAPI().getBaseURL(),
            queryString.toString(), getID());
        return new BoxResourceIterable<BoxFile.Info>(getAPI(), url, limit) {
            @Override
            protected BoxFile.Info factory(JsonObject jsonObject) {
                BoxFile boxFile = new BoxFile(getAPI(), jsonObject.get("id").asString());
                return boxFile.new Info(jsonObject);
            }
        };
    }

    /**
     * Deletes retention policy assignment.
     */
    public void delete() {
        URL url = RETENTION_POLICY_ASSIGNMENT_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
        request.send().close();
    }

    /**
     * Contains information about the retention policy.
     */
    public class Info extends BoxResource.Info {

        /**
         * @see #getRetentionPolicy()
         */
        private BoxRetentionPolicy.Info retentionPolicy;

        /**
         * @see #getAssignedBy()
         */
        private BoxUser.Info assignedBy;

        /**
         * @see #getAssignedAt()
         */
        private Date assignedAt;

        /**
         * @see #getAssignedToType()
         */
        private String assignedToType;

        /**
         * @see #getAssignedToID()
         */
        private String assignedToID;

        /**
         * @see #getStartDateField()
         */
        private String startDateField;

        private List<MetadataFieldFilter> filterFields;

        /**
         * Constructs an empty Info object.
         */
        public Info() {
            super();
        }

        /**
         * Constructs an Info object by parsing information from a JSON string.
         *
         * @param json the JSON string to parse.
         */
        public Info(String json) {
            super(json);
        }

        /**
         * Constructs an Info object using an already parsed JSON object.
         *
         * @param jsonObject the parsed JSON object.
         */
        Info(JsonObject jsonObject) {
            super(jsonObject);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public BoxResource getResource() {
            return BoxRetentionPolicyAssignment.this;
        }

        /**
         * @return the retention policy that has been assigned to this content.
         */
        public BoxRetentionPolicy.Info getRetentionPolicy() {
            return this.retentionPolicy;
        }

        /**
         * @return the info about the user that created the retention policy assignment.
         */
        public BoxUser.Info getAssignedBy() {
            return this.assignedBy;
        }

        /**
         * @return the time that the retention policy assignment was created.
         */
        public Date getAssignedAt() {
            return this.assignedAt;
        }

        /**
         * @return type of the content that is under retention. Can either be "enterprise" or "folder".
         */
        public String getAssignedToType() {
            return this.assignedToType;
        }

        /**
         * @return id of the folder that is under retention.
         */
        public String getAssignedToID() {
            return this.assignedToID;
        }

        /**
         * @return date the retention policy assignment begins
         */
        public String getStartDateField() {
            return this.startDateField;
        }

        /**
         * @return the array of metadata field filters, if present
         */
        public List<MetadataFieldFilter> getFilterFields() {

            return this.filterFields;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        void parseJSONMember(JsonObject.Member member) {
            super.parseJSONMember(member);
            String memberName = member.getName();
            JsonValue value = member.getValue();
            try {
                if (memberName.equals("retention_policy")) {
                    JsonObject policyJSON = value.asObject();
                    if (this.retentionPolicy == null) {
                        String policyID = policyJSON.get("id").asString();
                        BoxRetentionPolicy policy = new BoxRetentionPolicy(getAPI(), policyID);
                        this.retentionPolicy = policy.new Info(policyJSON);
                    } else {
                        this.retentionPolicy.update(policyJSON);
                    }
                } else if (memberName.equals("assigned_to")) {
                    JsonObject assignmentJSON = value.asObject();
                    this.assignedToType = assignmentJSON.get("type").asString();
                    if (this.assignedToType.equals(TYPE_ENTERPRISE)) {
                        this.assignedToID = null;
                    } else {
                        this.assignedToID = assignmentJSON.get("id").asString();
                    }
                } else if (memberName.equals("assigned_by")) {
                    JsonObject userJSON = value.asObject();
                    if (this.assignedBy == null) {
                        String userID = userJSON.get("id").asString();
                        BoxUser user = new BoxUser(getAPI(), userID);
                        this.assignedBy = user.new Info(userJSON);
                    } else {
                        this.assignedBy.update(userJSON);
                    }
                } else if (memberName.equals("assigned_at")) {
                    this.assignedAt = BoxDateFormat.parse(value.asString());
                } else if (memberName.equals("start_date_field")) {
                    this.startDateField = value.asString();
                } else if (memberName.equals("filter_fields")) {
                    JsonArray jsonFilters = value.asArray();
                    List<MetadataFieldFilter> filterFields = new ArrayList<>();
                    for (int i = 0; i < jsonFilters.size(); i++) {
                        filterFields.add(new MetadataFieldFilter(jsonFilters.get(i).asObject()));
                    }
                    this.filterFields = filterFields;
                }
            } catch (Exception e) {
                throw new BoxDeserializationException(memberName, value.toString(), e);
            }
        }
    }
}
