class HelpdeskSearchController < ApplicationController
  unloadable

  accept_api_auth :ticket_by_issue, :issues_by_contact, :messages_by_issue, :contact_timeline

  before_filter :require_login

  def usage
    # Human/browser probes often stop at /helpdesk_search/issues. Return a
    # machine-readable usage response instead of letting Rails raise a route
    # exception that looks like an application failure in production.log.
    render :json => {
      :helpdesk_search => {
        :ticket_by_issue => '/helpdesk_search/issues/:issue_id/ticket',
        :ticket_by_issue_alias => '/helpdesk_search/issues/:issue_id',
        :issues_by_contact => '/helpdesk_search/contacts/:contact_id/issues',
        :messages_by_issue => '/helpdesk_search/issues/:issue_id/messages',
        :contact_timeline => '/helpdesk_search/contacts/:contact_id/timeline'
      }
    }, :status => :bad_request
  end

  def ticket_by_issue
    issue = Issue.find(params[:issue_id])
    return unless authorize_helpdesk_project!(issue.project)

    ticket = HelpdeskTicket.where(:issue_id => issue.id).first
    unless ticket
      render_404
      return
    end

    render :json => {:helpdesk_ticket => serialize_ticket(ticket)}
  rescue ActiveRecord::RecordNotFound
    render_404
  end

  def issues_by_contact
    contact = Contact.find(params[:contact_id])
    tickets = HelpdeskTicket.
      includes(:issue => [:project, :status, :tracker, :assigned_to]).
      where(:contact_id => contact.id).
      order("#{HelpdeskTicket.table_name}.ticket_date DESC").
      limit(api_limit)

    render :json => {
      :contact_id => contact.id,
      :issues => tickets.map { |ticket| serialize_ticket_issue(ticket) }.compact
    }
  rescue ActiveRecord::RecordNotFound
    render_404
  end

  def messages_by_issue
    issue = Issue.find(params[:issue_id])
    return unless authorize_helpdesk_project!(issue.project)

    messages = JournalMessage.
      includes(:contact, :journal).
      joins(:journal).
      where(:journals => {:journalized_type => 'Issue', :journalized_id => issue.id}).
      order("#{JournalMessage.table_name}.message_date ASC").
      limit(api_limit)

    render :json => {
      :issue_id => issue.id,
      :journal_messages => messages.map { |message| serialize_journal_message(message) }
    }
  rescue ActiveRecord::RecordNotFound
    render_404
  end

  def contact_timeline
    contact = Contact.find(params[:contact_id])
    tickets = HelpdeskTicket.
      includes(:issue => [:project, :status, :tracker]).
      where(:contact_id => contact.id).
      order("#{HelpdeskTicket.table_name}.ticket_date DESC").
      limit(api_limit)

    messages = JournalMessage.
      includes(:contact, :journal).
      where(:contact_id => contact.id).
      order("#{JournalMessage.table_name}.message_date DESC").
      limit(api_limit)

    events = []
    tickets.each do |ticket|
      next unless visible_issue?(ticket.issue)
      events << serialize_ticket(ticket).merge(:type => 'helpdesk_ticket', :date => iso8601(ticket.ticket_date))
    end

    messages.each do |message|
      issue = message_issue(message)
      next unless visible_issue?(issue)
      events << serialize_journal_message(message).merge(:type => 'journal_message', :date => iso8601(message.message_date))
    end

    events.sort_by! { |event| event[:date].to_s }
    events.reverse!

    render :json => {
      :contact_id => contact.id,
      :timeline => events.first(api_limit)
    }
  rescue ActiveRecord::RecordNotFound
    render_404
  end

  private

  def authorize_helpdesk_project!(project)
    # Search endpoints expose customer/email context, so they reuse the native
    # per-project helpdesk visibility permission instead of adding a new role.
    return true if project && User.current.allowed_to?(:view_helpdesk_tickets, project)
    deny_access
    false
  end

  def visible_issue?(issue)
    issue && issue.project && User.current.allowed_to?(:view_helpdesk_tickets, issue.project)
  end

  def api_limit
    requested_limit = params[:limit].present? ? params[:limit].to_i : 100
    [[requested_limit, 1].max, 200].min
  end

  def serialize_ticket_issue(ticket)
    issue = ticket.issue
    return nil unless visible_issue?(issue)

    serialize_ticket(ticket).merge(
      :issue => {
        :id => issue.id,
        :project_id => issue.project_id,
        :tracker_id => issue.tracker_id,
        :tracker_name => issue.tracker.try(:name),
        :status_id => issue.status_id,
        :status_name => issue.status.try(:name),
        :assigned_to_id => issue.assigned_to_id,
        :assigned_to_name => issue.assigned_to.try(:name),
        :subject => issue.subject,
        :created_on => iso8601(issue.created_on),
        :updated_on => iso8601(issue.updated_on)
      }
    )
  end

  def serialize_ticket(ticket)
    {
      :id => ticket.id,
      :issue_id => ticket.issue_id,
      :contact_id => ticket.contact_id,
      :message_id => ticket.message_id,
      :source => ticket.source,
      :is_incoming => ticket.is_incoming?,
      :from_address => ticket.from_address,
      :to_address => ticket.to_address,
      :cc_address => ticket.cc_address,
      :ticket_date => iso8601(ticket.ticket_date)
    }
  end

  def serialize_journal_message(message)
    journal = message.journal
    issue = message_issue(message)

    # Keep this API metadata-only. The external indexer can fetch journal.notes
    # with its own read policy; this endpoint should not leak message bodies,
    # private notes, attachments, or BCC addresses by accident.
    {
      :id => message.id,
      :journal_id => message.journal_id,
      :issue_id => issue.try(:id),
      :project_id => issue.try(:project_id),
      :contact_id => message.contact_id,
      :message_id => message.message_id,
      :source => message.source,
      :is_incoming => message.is_incoming?,
      :from_address => message.from_address,
      :to_address => message.to_address,
      :cc_address => message.cc_address,
      :has_bcc_address => message.bcc_address.present?,
      :message_date => iso8601(message.message_date),
      :journal_user_id => journal.try(:user_id),
      :journal_private_notes => journal.try(:private_notes?),
      :journal_has_notes => journal.try(:notes).present?
    }
  end

  def message_issue(message)
    journal = message.journal
    return nil unless journal
    journal.try(:issue) || journal.try(:journalized)
  end

  def iso8601(value)
    value.try(:utc).try(:iso8601)
  end
end
