class InformationRequest < ActiveRecord::Base

  attr_accessible :age, :city_id, :residence, :delivery_way, :document_number, :document_type,
    :educational_level, :email, :entity_type, :firstname, :gender, :institution_id, :lastname,
    :nationality, :occupation_id, :phone, :requested_information, :confirmed, :reference_code, :resource

  attr_accessible :extension_type, :delivery_deadline, :estimated_business_days,
    :extension_justification, :resolution_date, :business_days, :information_type, :resolution_type, :address,
    :cellular, :fax, :nationality_name, :seen_document, :notification_mode, :admitted_at, :email_confirmation, :terms,
    :state

  # Non database attributes
  attr_accessible :state_id, :document_front_image, :document_back_image

  attr_accessor :email_confirmation, :terms, :birthday

  # serialize
  serialize :notification_mode

  # CONSTANTS
  STATES = %w[new correct process extended closed not_corrected not_admitted]

  EXTENSION_TYPE = {
    'none' => I18n.t('enum.information_request.extension_type.none'),
    'antiquity' => I18n.t('enum.information_request.extension_type.antiquity')
  }
  INFORMATION_TYPE = {
    'informal' => I18n.t('enum.information_request.information_type.informal'),
    'public' => I18n.t('enum.information_request.information_type.public'),
    'reserved' => I18n.t('enum.information_request.information_type.reserved'),
    'confidential' => I18n.t('enum.information_request.information_type.confidential'),
    'non_existent' => I18n.t('enum.information_request.information_type.non_existent'),
    're_routed' => I18n.t('enum.information_request.information_type.re_routed'),
    'undelivered' => I18n.t('enum.information_request.information_type.undelivered'),
    'not_processed' => I18n.t('enum.information_request.information_type.not_processed')
  }
  RESOLUTION_TYPE = {
    INFORMATION_TYPE['informal'] => [
      [I18n.t('enum.information_request.resolution_type.art_10_17_laip'), 'art_10_17_laip'],
      [I18n.t('enum.information_request.resolution_type.on_the_web'), 'on_the_web']
    ],
    INFORMATION_TYPE['public'] => [
      [I18n.t('enum.information_request.resolution_type.no_proprietary'), 'no_proprietary']
    ],
    INFORMATION_TYPE['reserved'] => [
      [I18n.t('enum.information_request.resolution_type.art_19_laip'), 'art_19_laip'],
      [I18n.t('enum.information_request.resolution_type.art_30_laip_reserved'), 'art_30_laip_reserved']
    ],
    INFORMATION_TYPE['confidential'] => [
      [I18n.t('enum.information_request.resolution_type.art_24_laip'), 'art_24_laip'],
      [I18n.t('enum.information_request.resolution_type.art_30_laip_confidential'), 'art_30_laip_confidential'],
      [I18n.t('enum.information_request.resolution_type.art_31_32_laip'), 'art_31_32_laip']
    ],
    INFORMATION_TYPE['non_existent'] => [
      [I18n.t('enum.information_request.resolution_type.art_73_laip'), 'art_73_laip']
    ],
    INFORMATION_TYPE['re_routed'] => [
      [I18n.t('enum.information_request.resolution_type.art_67_laip'), 'art_67_laip']
    ],
    INFORMATION_TYPE['undelivered'] => [
      [I18n.t('enum.information_request.resolution_type.rejected'), 'rejected']
    ],
    INFORMATION_TYPE['not_processed'] => [
      [I18n.t('enum.information_request.resolution_type.art_74_laip'), 'art_74_laip'],
      [I18n.t('enum.information_request.resolution_type.offensive'), 'offensive'],
      [I18n.t('enum.information_request.resolution_type.publicly_available'), 'publicly_available'],
      [I18n.t('enum.information_request.resolution_type.unreasonable'), 'unreasonable']
    ]
  }
  NOTIFICATION_MODE = {
    'email' => I18n.t('enum.information_request.notification_mode.email'),
    'phone' => I18n.t('enum.information_request.notification_mode.phone'),
    'cellular' => I18n.t('enum.information_request.notification_mode.cellular'),
    'fax' => I18n.t('enum.information_request.notification_mode.fax'),
    'mail' => I18n.t('enum.information_request.notification_mode.mail'),
    'oir_uaip' => I18n.t('enum.information_request.notification_mode.oir_uaip'),
  }

  CORRECT_BUSINESS_DAYS = 5

  # Accessors
  attr_accessor :state_id

  # Associations
  belongs_to :institution
  belongs_to :city
  belongs_to :occupation
  has_many :comments, as: :commentable, dependent: :destroy
  has_many :events, as: :eventable, class_name: "InformationRequestEvent", dependent: :destroy

  # Validations
  validates :residence,
            :delivery_way,
            :entity_type,
            :firstname,
            :gender,
            :lastname,
            :nationality,
            :occupation_id,
            :requested_information,
            :notification_mode,
            :institution_id,
          presence: true

  #validates :email, presence: true, format: { with: /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i }, if: Proc.new{|o| o.from_website? or o.notification_mode_email?}
  validates :email, presence: true, format: { with: /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i }, confirmation: true, if: Proc.new{|o| o.from_website?}
  validates :educational_level, presence: true, if: :entity_type_natural?
  validates :age, numericality: { only_integer: true }, allow_blank: true
  validates :age, presence: true, if: Proc.new{|o| o.entity_type_natural? and o.from_website?}
  validates :address, presence: true, if: Proc.new{|o| o.delivery_way_mail? or o.notification_mode_mail?}
  validates :phone, :cellular, :fax, length: { maximum: 200 }
  validates :city_id, :state_id, presence: true, if: :natural_and_salvadorian?
  validates :document_number, :document_type, presence: true, unless: :seen_document?
  validates :nationality_name, presence: true, if: :nationality_foreign?
  #validates :phone, presence: true, if: :notification_mode_phone?
  #validates :cellular, presence: true, if: :notification_mode_cellular?
  #validates :fax, presence: true, if: :notification_mode_fax?
  # validates document_number format
  # validates :document_number, format: { with: /\d{8}-\d{1}$/, message: I18n.t('errors.messages.invalid_dui') }, if: :document_type_dui?
  validates :document_number, length: { minimum: 6 }, if: :document_type_passport?
  validates :document_number, length: { minimum: 6 }, if: :document_type_resident_card?
  validates :document_number, length: { minimum: 6 }, if: :document_type_student_card?
  validates :document_number, length: { minimum: 6 }, if: :document_type_minority_card?
  validates_acceptance_of :terms

  # King Tokens
  can_has_tokens :confirm_email, { days_valid: 5 }

  # Acts as TextCaptcha
  acts_as_textcaptcha

  # Paperclip
  has_attached_file :document_front_image
  has_attached_file :document_back_image

  validates_attachment :document_front_image,
    presence: true,
    content_type: { content_type: ['image/jpeg', 'image/jpg', 'image/png', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'] },
    size: { in: 0..5.megabytes },
    if: :require_attachment?

  validates_attachment :document_back_image,
    content_type: { content_type: ['image/jpeg', 'image/jpg', 'image/png', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'] },
    size: { in: 0..5.megabytes },
    if: :require_attachment?

  # Scope
  scope :confirmed, where(confirmed: 1)
  scope :expired, lambda { where('information_requests.delivery_deadline IS NOT NULL AND information_requests.delivery_deadline < ?', Date.current) } # change confirmed_at by admitted_at
  scope :valid, where(ignore_it: false)

  # delegates
  delegate :new?, :correct?, :not_corrected?, to: :current_state
  delegate :information_request_correlative, :acronym, to: :institution, allow_nil: true, prefix: true

  # Enumerated attributes
  #
  enumerated_attribute :educational_level, %w(unknown none primary secondary highschool university postgrade) do
    label unknown: I18n.t('enum.information_request.educational_level.unknown')
    label none: I18n.t('enum.information_request.educational_level.none')
    label primary: I18n.t('enum.information_request.educational_level.primary')
    label secondary: I18n.t('enum.information_request.educational_level.secondary')
    label highschool: I18n.t('enum.information_request.educational_level.highschool')
    label university: I18n.t('enum.information_request.educational_level.university')
    label postgrade: I18n.t('enum.information_request.educational_level.postgrade')
  end

  enumerated_attribute :nationality, %w(^salvadorian foreign) do
    label salvadorian: I18n.t('enum.information_request.nationality.salvadorian')
    label foreign: I18n.t('enum.information_request.nationality.foreign')
  end

  enumerated_attribute :residence, %w(^el_salvador other) do
    label el_salvador: I18n.t('enum.information_request.residence.el_salvador')
    label other: I18n.t('enum.information_request.residence.other')
  end

  enumerated_attribute :entity_type, %w(^natural legal) do
    label natural: I18n.t('enum.information_request.entity_type.natural')
    label legal: I18n.t('enum.information_request.entity_type.legal')
  end

  enumerated_attribute :gender, %w(^F M) do
    label F: I18n.t('enum.information_request.gender.female')
    label M: I18n.t('enum.information_request.gender.male')
  end

  enumerated_attribute :document_type, %w(dui passport resident_card student_card minority_card other) do
    label dui: I18n.t('enum.information_request.document_type.dui')
    label passport: I18n.t('enum.information_request.document_type.passport')
    label resident_card: I18n.t('enum.information_request.document_type.resident_card')
    label student_card: I18n.t('enum.information_request.document_type.student_card')
    label minority_card: I18n.t('enum.information_request.document_type.minority_card')
    label other: I18n.t('enum.information_request.document_type.other')
  end

  enumerated_attribute :delivery_way, %w(cd dvd usb photocopy certified_photocopy email fax mail oir_uaip other) do
    label cd: I18n.t('enum.information_request.delivery_way.cd')
    label dvd: I18n.t('enum.information_request.delivery_way.dvd')
    label usb: I18n.t('enum.information_request.delivery_way.usb')
    label photocopy: I18n.t('enum.information_request.delivery_way.photocopy')
    label certified_photocopy: I18n.t('enum.information_request.delivery_way.certified_photocopy')
    label email: I18n.t('enum.information_request.delivery_way.email')
    label fax: I18n.t('enum.information_request.delivery_way.fax')
    label mail: I18n.t('enum.information_request.delivery_way.mail')
    label oir_uaip: I18n.t('enum.information_request.delivery_way.oir_uaip')
    label other: I18n.t('enum.information_request.delivery_way.other')
  end

  enumerated_attribute :extension_type, %w(none antiquity complexity antiquity_and_complexity) do
    label none: I18n.t('enum.information_request.extension_type.none')
    label antiquity: I18n.t('enum.information_request.extension_type.antiquity')
    label antiquity_and_complexity: I18n.t('enum.information_request.extension_type.antiquity_and_complexity')
  end

  def require_attachment?
    delivery_way_email? and !seen_document?
  end

  def natural_and_salvadorian?
    nationality_salvadorian? and entity_type_natural? and residence_el_salvador?
  end

  def generate_reference_code(length = 6)
    generated_code = (0..length).map{ rand(36).to_s(36) }.join
    return generated_code unless InformationRequest.exists?(reference_code: generated_code)

    generate_reference_code
  end

  def set_reference_code
    if self.reference_code.blank?
      self.reference_code = generate_reference_code.upcase
    end
  end

  def set_correlative
    if self.correlative.blank? and self.institution.present?
      self.correlative = "#{institution_acronym}-#{Date.current.year}-#{institution_information_request_correlative.to_s.rjust(4, '0')}"
      self.institution.next_correlative
    end
  end

  def deliver_confirmation_email_request
    self.set_confirm_email_token
    UserMailer.information_request_email_confirm(self).deliver
  end

  def deliver_information_request
    UserMailer.new_information_request(self).deliver
  end

  def deliver_information_request_user_notification
    UserMailer.new_information_request_user_notification(self).deliver if email.present?
  end

  def deliver_information_request_user_create
    UserMailer.new_information_request_user_create(self).deliver if email.present?
  end

  def document_image_extension(side = :front)
    File.extname(self.send("document_#{side.to_s}_image").path)
  end

  def readable_document_image_filename(side = :front)
    [ lastname,
      firstname,
      '-',
      self.enums(:document_type).label(self.document_type),
      '(' + I18n.t("activerecord.attributes.information_request.document_image.#{side.to_s}") + ')'
    ].join(' ') + self.document_image_extension(side)
  end

  def user_fullname
    [firstname, lastname].join(' ')
  end

  def name
    self[:requested_information]
  end

  def current_state
    #@current_state ||= (events.order(:id).last.try(:state) || STATES.first).inquiry
    state.inquiry
  end

  def process?
    #@process ||= ['process', 'extended'].include?(current_state)
    @process ||= ['process', 'extended'].include?(state)
  end

  def closed?
    #@closed ||= ['closed', 'not_corrected', 'not_admitted'].include?(current_state)
    @closed ||= ['closed', 'not_corrected', 'not_admitted'].include?(state)
  end

  def get_state
    #@get_state ||= I18n.t("label.#{current_state}")
    @get_state ||= I18n.t("label.#{state}")
  end

  def remaining_days
    @remaining_days ||= correct? ? ( events.last.try(:event_start_with_business_days_added, CORRECT_BUSINESS_DAYS) - Date.current ).to_i : (delivery_deadline	- Date.current).to_i rescue 0
  end

  def self.new_requests
    #joins("LEFT JOIN information_request_events ON information_request_events.eventable_id = information_requests.id AND information_request_events.eventable_type = 'InformationRequest'").where("information_request_events.id IS NULL")
    #where("information_requests.id NOT IN ( SELECT DISTINCT(eventable_id) FROM information_request_events WHERE eventable_type='InformationRequest' )")
    where(state: 'new')
  end

  def self.process_requests
    #joins(:events).merge InformationRequestEvent.with_last_state(['process','extended'])
    where(state: ['process', 'extended'])
  end

  def self.correct_requests
    #joins(:events).merge InformationRequestEvent.with_last_state('correct')
    where(state: 'correct')
  end

  def self.closed_requests
    #joins(:events).merge InformationRequestEvent.with_last_state(['closed','not_corrected','not_admitted'])
    where(state: ['closed', 'not_corrected', 'not_admitted'])
  end

  def self.expired_requests
    #expired.joins(:events).merge InformationRequestEvent.with_last_state(['process','extended'])
    where('information_requests.state in (?) and information_requests.delivery_deadline < ?', ['process', 'extended'], Date.current)
  end

  def can_process?
    # can process if status is new or correct
    @can_process ||= self.new? or self.correct? and self.is_admitted?
  end

  def can_correct?
    # can correct if status is new or if process status is in 3 business days range, and there is not a previous correction
    @can_correct ||= (start_date_with_business(3) - Date.current).to_i >= 0 and self.has_not_been_corrected? and !closed? and is_admitted? rescue false
  end

  def can_closed?
    # can closed if status is in process and there is no requirements open
    #@can_closed ||= ( !['new', 'correct'].include?(current_state) and has_requirements? and opened_requirements == 0 )
    @can_closed ||= ( !['new', 'correct'].include?(state) and has_requirements? and opened_requirements == 0 )
  end

  def has_not_been_corrected?
    @has_not_been_corrected ||= !events.pluck(:state).include?('correct')
  end

  def can_extended?
    @can_extended ||= (extension_type.to_s != 'antiquity_and_complexity') and process?
  end

  def percent
    @percent ||= self.process? ? (events.last.try(:percent, delivery_deadline) rescue nil) : nil
  end

  # verify has requirements
  def has_requirements?
    @has_requirements ||= comments.requirements.any?
  end

  # count of new requirements
  def opened_requirements
    @opened_requirements ||= comments.requirements.map(&:current_state).count('process')
  end

  # obtain last event business days, to current date
  def last_business_days
    events.order("id DESC").limit(1).first.try(:business_days_passed)
  end

  def label_class
    @label_class ||= closed? ? '' : ( remaining_days.to_i < 0 ? 'label-important' : ( new? ? 'label-info' : ( process? ? 'label-success' : ( correct? ? 'label-warning' : '' ) ) ) )
  end

  def get_information_type
    InformationRequest::INFORMATION_TYPE[information_type]
  end

  def get_resolution_type
    s = ""
    InformationRequest::RESOLUTION_TYPE[get_information_type].each{|a| s = a[0] if a[1] == resolution_type }
    s
  end

  def department_name
    city.state.name rescue I18n.t('label.none')
  end

  def start_date
    @start_date ||= correct? ? Time.current : admitted_at
  end

  def start_date_with_business(days, custom_date = nil)
    business_date = custom_date ? custom_date : ( correct? ? Date.current : ( admitted_at.try(:to_date) || Date.current ) )
    # obtain all business days
    business_days = CalendarEvent.select('kind,start_at').joins(:institution_calendar).where('institution_calendars.institution_id = ? AND calendar_events.start_at > ?', institution_id, business_date.beginning_of_day).group_by{|o| o.start_at.to_date}
    while days > 1 # 0 first business day is current day
      business_date = business_date + 1.day
      if business_days.has_key?(business_date)
        # rest a day unless is a holiday
        days -= 1 unless business_days[business_date].map(&:kind).include?(:holiday)
      else
        # rest a day unless is weekened
        days -= 1 unless business_date.saturday? or business_date.sunday?
      end
    end
    business_date
  end

  def add_complexity_days(days = 5)
    while days > 1 # 0 first business day is current day
      self.delivery_deadline = self.delivery_deadline + 1.day
      days -= 1 unless delivery_deadline.saturday? or delivery_deadline.sunday?
    end
  end

  def notification_mode_string
    return '' unless notification_mode.present?
    Array.wrap(notification_mode).map{|n| NOTIFICATION_MODE[n]}.join(', ')
  end

  def is_admitted?
    admitted_at.present?
  end

  def processed_at
    events.where(state: 'process').order('id DESC').limit(1).first.event_start rescue nil
  end

  def corrected_at
    events.where(state: 'correct').order('id DESC').limit(1).first.event_start rescue nil
  end

  def closed_at
    events.where(state: 'closed').order('id DESC').limit(1).first.created_at.to_date rescue nil
  end

  def average_time_per_requirement
    requirement_days = comments.requirements.map(&:process_days).compact
    return nil unless requirement_days.size > 0
    (requirement_days.inject(:+).to_f / requirement_days.size).round(1) rescue nil
  end

  def process_days
    @status = events.order(:id)
    return nil unless @status.size > 1
    # obtain process and closed date
    process_date = @status.select{|s| s.state == 'process'}.last.try(:event_start)
    closed_date = @status.select{|s| s.state == 'closed'}.last.try(:created_at)
    return nil if process_date.nil? or closed_date.nil?
    # obtain all business days between event dates
    business_days = CalendarEvent.select('kind,start_at').joins(:institution_calendar).where('institution_calendars.institution_id = ? AND calendar_events.start_at BETWEEN ? AND ?', institution_id, process_date.beginning_of_day, closed_date.end_of_day).group_by{|o| o.start_at.to_date}
    cursor_day = process_date
    counted_days = 0
    while cursor_day <= closed_date.to_date
      if business_days.has_key?(cursor_day)
        # rest a day unless is a holiday
        counted_days += 1 unless business_days[cursor_day].map(&:kind).include?(:holiday)
      else
        # rest a day unless is weekened
        counted_days += 1 unless cursor_day.saturday? or cursor_day.sunday?
      end
      cursor_day = cursor_day + 1.day
    end
    counted_days
  end

  def set_start_at(date = nil)
    return unless date
    begin
      nd = Date.parse(date)
      # update admitted at
      update_column(:admitted_at, Time.zone.local(nd.year, nd.month, nd.day, admitted_at.hour, admitted_at.min, admitted_at.sec))
      # update delivery deadline
      update_column(:delivery_deadline, start_date_with_business(10))
      # update process date
      e = events.select{|o| o.state == 'process'}.first
      if e
        e.update_column(:event_start, nd)
        e.update_column(:created_at, Time.zone.local(nd.year, nd.month, nd.day, e.created_at.hour, e.created_at.min, e.created_at.sec))
        e.update_column(:updated_at, Time.zone.local(nd.year, nd.month, nd.day, e.updated_at.hour, e.updated_at.min, e.updated_at.sec))
      end
      # update requirements start
      comments.requirements.each do |r|
        r.update_column(:created_at, Time.zone.local(nd.year, nd.month, nd.day, r.created_at.hour, r.created_at.min, r.created_at.sec))
        r.update_column(:updated_at, Time.zone.local(nd.year, nd.month, nd.day, r.updated_at.hour, r.updated_at.min, r.updated_at.sec))
        e = r.events.select{|o| o.state == 'process'}.first
        if e
          e.update_column(:event_start, nd)
          e.update_column(:created_at, Time.zone.local(nd.year, nd.month, nd.day, e.created_at.hour, e.created_at.min, e.created_at.sec))
          e.update_column(:updated_at, Time.zone.local(nd.year, nd.month, nd.day, e.updated_at.hour, e.updated_at.min, e.updated_at.sec))
        end
      end
    end
  end

  def set_end_at(date = nil)
    return unless date
    begin
      nd = Date.parse(date)
      # update closed date
      e = events.select{|o| o.state == 'closed'}.first
      if e
        e.update_column(:event_start, nd)
        e.update_column(:created_at, Time.zone.local(nd.year, nd.month, nd.day, e.created_at.hour, e.created_at.min, e.created_at.sec))
        e.update_column(:updated_at, Time.zone.local(nd.year, nd.month, nd.day, e.updated_at.hour, e.updated_at.min, e.updated_at.sec))
      end
      # update requirements end
      comments.requirements.each do |r|
        e = r.events.select{|o| o.state == 'closed'}.first
        if e
          e.update_column(:event_start, nd)
          e.update_column(:created_at, Time.zone.local(nd.year, nd.month, nd.day, e.created_at.hour, e.created_at.min, e.created_at.sec))
          e.update_column(:updated_at, Time.zone.local(nd.year, nd.month, nd.day, e.updated_at.hour, e.updated_at.min, e.updated_at.sec))
        end
        # update individual requirements created and updated
        r.information_request_requirements.each do |r2|
          r2.update_column(:created_at, Time.zone.local(nd.year, nd.month, nd.day, r2.created_at.hour, r2.created_at.min, r2.created_at.sec))
          r2.update_column(:updated_at, Time.zone.local(nd.year, nd.month, nd.day, r2.updated_at.hour, r2.updated_at.min, r2.updated_at.sec))
        end
      end
    end
  end

  def update_delivery_deadline
    if has_not_been_corrected?
      date = admitted_at.try(:to_date)
    else
      date = processed_at
    end
    if date
      days = case extension_type.to_s
        when 'antiquity'
          20
        when 'complexity'
          15
        when 'antiquity_and_complexity'
          25
        else
          10
      end
      update_column(:delivery_deadline, start_date_with_business(days, date)) if days
    end
  end

  def extension_type_s
    if extension_type.blank?
      I18n.t('enum.information_request.extension_type.none')
    else
      I18n.t("enum.information_request.extension_type.#{extension_type.to_s}")
    end
  end

  def extension_type_justification
    if extension_type.blank? or extension_type.to_s == 'none'
      'N/A'
    else
      events.where(state: 'extended').order(id: :desc).first.try(:justification)
    end
  end

end
