require 'uri'

require 'mailgun/exceptions/exceptions'

module Mailgun

  # The Mailgun::Suppressions object makes it easy to manage "suppressions"
  # attached to an account. "Suppressions" means bounces, unsubscribes, and complaints.
  class Suppressions

    # @param [Mailgun::Client] client API client to use for requests
    # @param [String] domain Domain name to use for the suppression endpoints.
    def initialize(client, domain)
      @client = client
      @domain = domain

      @paging_next = nil
      @paging_prev = nil
    end

    ####
    # Paging operations
    ####

    def next
      response = get_from_paging @paging_next[:path], @paging_next[:params]
      extract_paging response
      response
    end

    def prev
      response = get_from_paging @paging_prev[:path], @paging_prev[:params]
      extract_paging response
      response
    end

    ####
    # Bounces Endpoint (/v3/:domain/bounces)
    ####

    def list_bounces(params = {})
      response = @client.get("#{@domain}/bounces", params)
      extract_paging response
      response
    end

    def get_bounce(address)
      @client.get("#{@domain}/bounces/#{address}", nil)
    end

    def create_bounce(params = {})
      @client.post("#{@domain/bounces}", params)
    end

    # Creates multiple bounces on the Mailgun API.
    # If a bounce does not have a valid structure, it will be added to a list of unsendable bounces.
    # The list of unsendable bounces will be returned at the end of this operation.
    #
    # If more than 999 bounce entries are provided, the list will be split and recursive calls will be made.
    #
    # @param [Array] data Array of bounce hashes
    # @return [Response] Mailgun API response
    # @return [Array] Return values from recursive call for list split.
    def create_bounces(data)
      # `data` should be a list of hashes, with each hash containing *at least* an `address` key.
      split_return = []
      if data.length >= 1000 then
        resp, resp_l = create_bounces data[999..-1]
        split_return.push(resp)
        split_return.concat(resp_l)
        data = data[0..998]
      elsif data.length == 0 then
        return nil, []
      end

      valid = []
      # Validate the bounces given
      # NOTE: `data` could potentially be very large (1000 elements) so it is
      # more efficient to pop from data and push into a different array as
      # opposed to possibly copying the entire array to another array.
      while not data.empty? do
        bounce = data.pop
        # Bounces MUST contain a `address` key.
        if not bounce.include? :address then
          raise Mailgun::ParameterError.new "Bounce MUST include a :address key: #{bounce}"
        end

        bounce.each do |k, v|
          # Hash values MUST be strings.
          if not v.is_a? String then
            bounce[k] = v.to_s
          end
        end

        valid.push bounce
      end

      response = @client.post("#{@domain}/bounces", valid.to_json, { "Content-Type" => "application/json" })
      return response, split_return
    end

    def delete_bounce(address)
      @client.delete("#{@domain}/bounces/#{address}")
    end

    def delete_all_bounces
      @client.delete("#{@domain}/bounces")
    end

    ####
    # Unsubscribes Endpoint (/v3/:domain/unsubscribes)
    ####

    def list_unsubscribes(params = {})
      response = @client.get("#{@domain}/unsubscribes", params)
      extract_paging response
      response
    end

    def get_unsubscribe(address)
      @client.get("#{@domain}/unsubscribes/#{address}")
    end

    def create_unsubscribe(params = {})
      @client.post("#{@domain}/unsubscribes", params)
    end

    # Creates multiple unsubscribes on the Mailgun API.
    # If an unsubscribe does not have a valid structure, it will be added to a list of unsendable unsubscribes.
    # The list of unsendable unsubscribes will be returned at the end of this operation.
    #
    # If more than 999 unsubscribe entries are provided, the list will be split and recursive calls will be made.
    #
    # @param [Array] data Array of unsubscribe hashes
    # @return [Response] Mailgun API response
    # @return [Array] Return values from recursive call for list split.
    def create_unsubscribes(data)
      # `data` should be a list of hashes, with each hash containing *at least* an `address` key.
      split_return = []
      if data.length >= 1000 then
        resp, resp_l = create_unsubscribes data[999..-1]
        split_return.push(resp)
        split_return.concat(resp_l)
        data = data[0..998]
      elsif data.length == 0 then
        return nil, []
      end

      valid = []
      # Validate the unsubscribes given
      while not data.empty? do
        unsubscribe = data.pop
        # unsubscribes MUST contain a `address` key.
        if not unsubscribe.include? :address then
          raise Mailgun::ParameterError.new "Unsubscribe MUST include a :address key: #{unsubscribe}"
        end

        unsubscribe.each do |k, v|
          # Hash values MUST be strings.
          # However, unsubscribes contain an array of tags
          if v.is_a? Array
            unsubscribe[k] = v.map(&:to_s)
          elsif !v.is_a? String
            unsubscribe[k] = v.to_s
          end
        end

        valid.push unsubscribe
      end

      response = @client.post("#{@domain}/unsubscribes", valid.to_json, { "Content-Type" => "application/json" })
      return response, split_return
    end

    def delete_unsubscribe(address, params = {})
      @client.delete("#{@domain}/unsubscribes/#{address}")
    end

    ####
    # Complaints Endpoint (/v3/:domain/complaints)
    ####

    def list_complaints(params = {})
      response = @client.get("#{@domain}/complaints", params)
      extract_paging response
      response
    end

    def get_complaint(address)
      @client.get("#{@domain}/complaints/#{address}", nil)
    end

    def create_complaint(params = {})
      @client.post("#{@domain}/complaints", params)
    end

    # Creates multiple complaints on the Mailgun API.
    # If a complaint does not have a valid structure, it will be added to a list of unsendable complaints.
    # The list of unsendable complaints will be returned at the end of this operation.
    #
    # If more than 999 complaint entries are provided, the list will be split and recursive calls will be made.
    #
    # @param [Array] data Array of complaint hashes
    # @return [Response] Mailgun API response
    # @return [Array] Return values from recursive call for list split.
    def create_complaints(data)
      # `data` should be a list of hashes, with each hash containing *at least* an `address` key.
      split_return = []
      if data.length >= 1000 then
        resp, resp_l = create_complaints data[999..-1]
        split_return.push(resp)
        split_return.concat(resp_l)
        data = data[0..998]
      elsif data.length == 0 then
        return nil, []
      end

      valid = []
      # Validate the complaints given
      while not data.empty? do
        complaint = data.pop
        # complaints MUST contain a `address` key.
        if not complaint.include? :address then
          raise Mailgun::ParameterError.new "Complaint MUST include a :address key: #{complaint}"
        end

        complaint.each do |k, v|
          # Hash values MUST be strings.
          if not v.is_a? String then
            complaint[k] = v.to_s
          end
        end

        valid.push complaint
      end

      response = @client.post("#{@domain}/complaints", valid.to_json, { "Content-Type" => "application/json" })
      return response, split_return
    end

    def delete_complaint(address)
      @client.delete("#{@domain}/complaints/#{address}")
    end

    private

    def get_from_paging(uri, params = {})
      @client.get(uri, params)
    end

    def extract_paging(response)
      rhash = response.to_h
      return nil unless rhash.include? "paging"

      page_info = rhash["paging"]

      # Build the `next` endpoint
      page_next = URI.parse(page_info["next"])
      @paging_next = {
        :path => page_next.path[/\/v[\d](.+)/, 1],
        :params => Hash[URI.decode_www_form page_next.query],
      }

      # Build the `prev` endpoint
      page_prev = URI.parse(page_info["previous"])
      @paging_prev = {
        :path => page_prev.path[/\/v[\d](.+)/, 1],
        :params => Hash[URI.decode_www_form page_prev.query],
      }
    end

  end
end
