require 'postman/error'
require 'postman/header_set'
require 'postman/attachment'

module Postman
  class Message

    #
    # Find a specific messsage with the given scope
    #
    def self.find_with_scope(scope, id)
      api = scope.client.moonrope.messages.message(:id => id.to_i, :_expansions => scope.expansions)
      if api.success?
        Message.new(scope.client, api.data)
      elsif api.status == 'error' && api.data['code'] == 'MessageNotFound'
        raise MessageNotFound.new(id)
      else
        raise Error, "Couldn't load message from API (#{api.data})"
      end
    end

    #
    # If methods are called directly on the Message class, we likely want to see if we can
    # run them through the global client message scope.
    #
    def self.method_missing(name, *args, &block)
      if MessageScope.instance_methods(false).include?(name)
        Postman::Client.instance.messages.send(name, *args, &block)
      else
        super
      end
    end

    #
    # Initialize a new message object with the client and a set of initial attributes.
    #
    def initialize(client, attributes)
      @client = client
      @attributes = attributes
    end

    #
    # Return the message ID
    #
    def id
      @attributes['id']
    end

    #
    # Return the message token
    #
    def token
      @attributes['token']
    end

    #
    # Set a has of all the attributes from the API that should be exposed through
    # the Message class.
    #
    ATTRIBUTES = {
      :status => [:status, :status],
      :last_delivery_attempt => [:status, :last_delivery_attempt, :timestamp],
      :held? => [:status, :held, :boolean],
      :hold_expiry => [:status, :hold_expiry, :timestamp],
      :rcpt_to => [:details, :rcpt_to],
      :mail_from => [:details, :mail_from],
      :subject => [:details, :subject],
      :message_id => [:details, :message_id],
      :timestamp => [:details, :timestamp, :timestamp],
      :direction => [:details, :direction],
      :size => [:details, :size],
      :bounce? => [:details, :bounce, :boolean],
      :bounce_for_id => [:details, :bounce],
      :tag => [:details, :tag],
      :received_with_ssl? => [:details, :received_with_ssl, :boolean],
      :inspected? => [:inspection, :inspected, :boolean],
      :spam? => [:inspection, :spam, :boolean],
      :spam_score => [:inspection, :spam_score],
      :threat? => [:inspection, :thret, :boolean],
      :threat_details => [:inspection, :threat_details],
      :plain_body => [:plain_body],
      :html_body => [:html_body],
    }

    #
    # Catch calls to any of the default attributes for a message and return the
    # data however we'd like it
    #
    def method_missing(name, *args, &block)
      if mapping = ATTRIBUTES[name.to_sym]
        expansion, attribute, type = mapping
        value = from_expansion(expansion, attribute)
        case type
        when :timestamp
          value ? Time.at(value) : nil
        when :boolean
          value == 1
        else
          value
        end
      else
        super
      end
    end

    #
    # Return a set of headers which can be queried like a hash however looking up
    # values using [] will be case-insensitive.
    #
    def headers
      @headers ||= HeaderSet.new(from_expansion(:headers))
    end

    #
    # Return an array of attachment objects
    #
    def attachments
      @attachments ||= from_expansion(:attachments).map do |a|
        Attachment.new(a)
      end
    end

    #
    # Return the full raw message
    #
    def raw_message
      @raw_message ||= Base64.decode64(from_expansion(:raw_message))
    end

    private

    def from_expansion(expansion, attribute = nil, loaded = false)
      if @attributes.has_key?(expansion.to_s) || loaded
        attribute ? @attributes[expansion.to_s][attribute.to_s] : @attributes[expansion.to_s]
      else
        load_expansions(expansion)
        from_expansion(expansion, attribute, true)
      end
    end

    def load_expansions(*names)
      puts "\e[31mLoading expansion #{names}\e[0m"
      api = @client.moonrope.messages.message(:id => self.id, :_expansions => names)
      if api.success?
        names.each do |expansion_name|
          if api.data.has_key?(expansion_name.to_s)
            @attributes[expansion_name.to_s] = api.data[expansion_name.to_s]
          end
        end
      else
        raise Postman::Error, "Couldn't load expansion data (#{names.join(', ')}) for message ID '#{self.id}'"
      end
    end

  end
end