# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

class Whatsapp::Webhook::Message
  include Mixin::RequiredSubPaths

  attr_reader :data, :channel, :user, :ticket, :article

  def initialize(data:, channel:)
    @data = data
    @channel = channel
  end

  def process
    @user = create_or_update_user

    UserInfo.current_user_id = user.id
    @ticket = create_or_update_ticket
    @article = create_or_update_article

    schedule_reminder_job
  end

  private

  def attachment?
    false
  end

  def attachment
    raise NotImplementedError
  end

  def body
    raise NotImplementedError
  end

  def content_type
    raise NotImplementedError
  end

  def create_or_update_user
    User.by_mobile(number: user_info[:mobile]) || create_user
  end

  def create_or_update_ticket
    ticket = find_ticket
    return update_ticket(ticket:) if ticket.present?

    create_ticket
  end

  def create_ticket
    title = Translation.translate(Setting.get('locale_default') || 'en-us', __('%s via WhatsApp'), "#{profile_name} (#{@user.mobile})")

    Ticket.create!(
      group_id:    @channel.group_id,
      title:,
      state_id:    Ticket::State.find_by(default_create: true).id,
      priority_id: Ticket::Priority.find_by(default_create: true).id,
      customer_id: @user.id,
      preferences: {
        channel_id:   @channel.id,
        channel_area: @channel.area,
        whatsapp:     ticket_preferences,
      },
    )
  end

  def update_ticket(ticket:)
    new_state_id = ticket.state_id == default_create_ticket_state.id ? ticket.state_id : default_follow_up_ticket_state.id

    preferences = ticket.preferences
    preferences[:whatsapp] ||= {}
    preferences[:whatsapp][:timestamp_incoming] = @data[:entry].first[:changes].first[:value][:messages].first[:timestamp]

    ticket.update!(
      preferences:,
      state_id:    new_state_id,
    )

    ticket
  end

  def find_ticket
    state_ids        = Ticket::State.where(name: %w[closed merged removed]).pluck(:id)
    possible_tickets = Ticket.where(customer_id: @user.id).where.not(state_id: state_ids).reorder(:updated_at)

    possible_tickets.find_each.find { |possible_ticket| possible_ticket.preferences[:channel_id] == @channel.id }
  end

  def default_create_ticket_state
    Ticket::State.find_by(default_create: true)
  end

  def default_follow_up_ticket_state
    Ticket::State.find_by(default_follow_up: true)
  end

  def create_or_update_article
    # Editing messages results in being an unsupported type in the Cloud API. Nothing to do here!

    create_article
  end

  def create_article
    is_first_article = @ticket.articles.count.zero?

    article = Ticket::Article.create!(
      ticket_id:    @ticket.id,
      type_id:      Ticket::Article::Type.lookup(name: 'whatsapp message').id,
      sender_id:    Ticket::Article::Sender.lookup(name: 'Customer').id,
      from:         "#{profile_name} (#{@user.mobile})",
      to:           "#{@channel.options[:name]} (#{@channel.options[:phone_number]})",
      message_id:   article_preferences[:message_id],
      internal:     false,
      body:         body,
      content_type: content_type,
      preferences:  {
        whatsapp: article_preferences,
      },
    )

    create_attachment(article: article) if attachment?

    create_welcome_article if is_first_article

    article
  end

  def create_attachment(article:)
    data, filename, mime_type = attachment

    Store.create!(
      object:      'Ticket::Article',
      o_id:        article.id,
      data:        data,
      filename:    filename,
      preferences: {
        'Mime-Type' => mime_type,
      },
    )
  rescue Whatsapp::Client::CloudAPIError
    preferences = article.preferences
    preferences[:whatsapp] ||= {}
    preferences[:whatsapp][:media_error] = true
    article.update!(preferences:)
  rescue Whatsapp::Incoming::Media::InvalidMediaTypeError => e
    article.update!(
      body:     e.message,
      internal: true,
    )
  end

  def create_welcome_article

    return if @channel.options[:welcome].blank?

    translated_welcome_message = Translation.translate(user_locale, @channel.options[:welcome])

    Ticket::Article.create!(
      ticket_id:    @ticket.id,
      type_id:      Ticket::Article::Type.lookup(name: 'whatsapp message').id,
      sender_id:    Ticket::Article::Sender.lookup(name: 'System').id,
      from:         "#{@channel.options[:name]} (#{@channel.options[:phone_number]})",
      to:           "#{profile_name} (#{@user.mobile})",
      subject:      translated_welcome_message.truncate(100, omission: '…'),
      internal:     false,
      body:         translated_welcome_message,
      content_type: 'text/plain',
    )
  end

  def create_user
    user_data = user_info

    user_data[:active]   = true
    user_data[:role_ids] = Role.signup_role_ids

    User.create(user_data)
  end

  def user_info
    firstname, lastname = User.name_guess(profile_name)

    # Fallback to profile name if no firstname or lastname is found
    if firstname.blank? || lastname.blank?
      firstname, lastname = profile_name.split(%r{\s|\.|,|,\s}, 2)
    end

    {
      firstname: firstname&.strip,
      lastname:  lastname&.strip,
      mobile:    "+#{phone}",
      login:     phone,
    }
  end

  def user_locale
    @user.preferences[:locale] || Locale.default
  end

  def profile_name
    data[:entry].first[:changes].first[:value][:contacts].first[:profile][:name]
  end

  def phone
    data[:entry].first[:changes].first[:value][:messages].first[:from]
  end

  def ticket_preferences
    {
      from:               {
        phone_number: phone,
        display_name: profile_name,
      },
      timestamp_incoming: @data[:entry].first[:changes].first[:value][:messages].first[:timestamp],
    }
  end

  def article_preferences
    {
      entry_id:   @data[:entry].first[:id],
      message_id: @data[:entry].first[:changes].first[:value][:messages].first[:id],
      type:       @data[:entry].first[:changes].first[:value][:messages].first[:type],
    }
  end

  def type
    raise NotImplementedError
  end

  def message
    @message ||= @data[:entry]
      .first[:changes]
      .first[:value][:messages]
      .first[type]
  end

  def schedule_reminder_job
    # Automatic reminders are an optional feature.
    return if !@channel.options[:reminder_active]

    # Do not schedule the job in case the service window has not been opened yet.
    timestamp_incoming = @ticket.preferences.dig(:whatsapp, :timestamp_incoming)
    return if timestamp_incoming.nil?

    cleanup_last_reminder_job

    # Calculate the end of the service window, based on the message timestamp.
    end_service_time = Time.zone.at(timestamp_incoming.to_i) + 24.hours
    return if end_service_time <= Time.zone.now

    # Set the reminder time to 1 hour before the service window closes and schedule a delayed job.
    reminder_time = end_service_time - 1.hour
    job = ScheduledWhatsappReminderJob.perform_at(reminder_time, @ticket, user_locale)

    # Remember reminder job information for subsequent runs.
    preferences = @ticket.preferences
    preferences[:whatsapp][:last_reminder_job_id] = job.provider_job_id
    ticket.update!(preferences:)
  end

  def cleanup_last_reminder_job
    last_job_id = @ticket.preferences.dig(:whatsapp, :last_reminder_job_id)
    return if last_job_id.nil?

    ::Delayed::Job.find_by(id: last_job_id)&.destroy!
  end
end