class Whatsapp::Providers::WhatsappZapiService < Whatsapp::Providers::BaseService # rubocop:disable Metrics/ClassLength
  class ProviderUnavailableError < StandardError; end

  API_BASE_PATH = 'https://api.z-api.io'.freeze

  def send_template(phone_number, template_info); end

  def sync_templates; end

  def send_message(phone, message)
    phone = phone.delete('+')
    params = message.content_attributes[:zapi_args].presence || {}

    params[:messageId] = message.in_reply_to_external_id if message.in_reply_to_external_id.present?

    if message.content_attributes[:is_reaction]
      send_reaction_message(phone, message, **params)
    elsif message.attachments.present?
      handle_message_with_attachment(message, phone, **params)
    elsif message.outgoing_content.present?
      send_text_message(phone, message, **params)
    else
      message.update!(is_unsupported: true)
      nil
    end
  end

  def validate_provider_config?
    response = HTTParty.get(
      "#{api_instance_path_with_token}/status",
      headers: api_headers
    )

    process_response(response)
  end

  def setup_channel_provider
    response = HTTParty.put(
      "#{api_instance_path_with_token}/update-every-webhooks",
      headers: api_headers,
      body: {
        value: whatsapp_channel.inbox.callback_webhook_url,
        notifySentByMe: true
      }.to_json
    )

    raise ProviderUnavailableError unless process_response(response)

    if whatsapp_channel.provider_connection.blank? || whatsapp_channel.provider_connection['connection'] == 'close'
      Channels::Whatsapp::ZapiQrCodeJob.perform_later(whatsapp_channel)
    end

    true
  end

  def disconnect_channel_provider
    response = HTTParty.get(
      "#{api_instance_path_with_token}/disconnect",
      headers: api_headers
    )

    raise ProviderUnavailableError unless process_response(response)

    true
  end

  def qr_code_image
    response = HTTParty.get(
      "#{api_instance_path_with_token}/qr-code/image",
      headers: api_headers
    )

    if response.parsed_response['connected']
      whatsapp_channel.update_provider_connection!(connection: 'open')
      return
    end

    return unless process_response(response)

    response.parsed_response['value']
  end

  def read_messages(messages, recipient_id:, **)
    phone = recipient_id.delete('+')

    messages.each do |message|
      next if message.source_id.blank?

      Channels::Whatsapp::ZapiReadMessageJob.perform_later(whatsapp_channel, phone, message.source_id)
    end

    true
  end

  def send_read_message(phone, message_source_id)
    response = HTTParty.post(
      "#{api_instance_path_with_token}/read-message",
      headers: api_headers,
      body: {
        phone: phone,
        messageId: message_source_id
      }.to_json
    )

    process_response(response)
  end

  def on_whatsapp(phone_number)
    response = HTTParty.get(
      "#{api_instance_path_with_token}/phone-exists/#{phone_number.delete('+')}",
      headers: api_headers
    )

    raise ProviderUnavailableError unless process_response(response)

    response.parsed_response || { 'exists' => false, 'phone' => nil, 'lid' => nil }
  end

  private

  def api_instance_path
    "#{API_BASE_PATH}/instances/#{whatsapp_channel.provider_config['instance_id']}"
  end

  def api_instance_path_with_token
    "#{api_instance_path}/token/#{whatsapp_channel.provider_config['token']}"
  end

  def api_headers
    { 'Content-Type' => 'application/json', 'Client-Token' => whatsapp_channel.provider_config['client_token'] }
  end

  def process_response(response)
    Rails.logger.error response.body unless response.success?
    response.success?
  end

  def send_text_message(phone, message, **params)
    response = HTTParty.post(
      "#{api_instance_path_with_token}/send-text",
      headers: api_headers,
      body: {
        phone: phone,
        message: message.outgoing_content,
        **params
      }.compact.to_json
    )

    unless process_response(response)
      message.update!(status: :failed, external_error: response.parsed_response&.dig('error'))
      raise ProviderUnavailableError
    end

    response.parsed_response&.dig('messageId')
  end

  def handle_message_with_attachment(message, phone, **params)
    attachment = message.attachments.first

    if attachment.file.byte_size > max_size(attachment)
      message.update!(status: :failed, external_error: 'File too large')
      return
    end

    base64_data = attachment_to_base64(attachment)
    buffer = "data:#{attachment.file.content_type};base64,#{base64_data}"

    case attachment.file_type
    when 'image'
      send_image_message(phone, message, buffer, **params)
    when 'audio'
      send_audio_message(phone, message, buffer, **params)
    when 'file'
      send_document_message(phone, message, attachment, buffer, **params)
    when 'video'
      send_video_message(phone, message, buffer, **params)
    end
  end

  def max_size(attachment)
    case attachment.file_type
    when 'image'
      5.megabytes
    when 'audio', 'video'
      16.megabytes
    else
      100.megabytes
    end
  end

  def send_image_message(phone, message, buffer, **params)
    response = HTTParty.post(
      "#{api_instance_path_with_token}/send-image",
      headers: api_headers,
      body: {
        phone: phone,
        image: buffer,
        caption: message.outgoing_content,
        **params
      }.compact.to_json
    )

    raise ProviderUnavailableError unless process_response(response)

    response.parsed_response&.dig('messageId')
  end

  def send_audio_message(phone, _message, buffer, **params)
    response = HTTParty.post(
      "#{api_instance_path_with_token}/send-audio",
      headers: api_headers,
      body: {
        phone: phone,
        audio: buffer,
        waveform: true,
        **params
      }.compact.to_json
    )

    raise ProviderUnavailableError unless process_response(response)

    response.parsed_response&.dig('messageId')
  end

  def send_document_message(phone, message, attachment, buffer, **params)
    file_extension = File.extname(attachment.file.filename.to_s).delete('.')
    if file_extension.blank?
      Rails.logger.warn "Missing file extension for attachment: #{attachment.id}"
      file_extension = 'bin'
    end

    response = HTTParty.post(
      "#{api_instance_path_with_token}/send-document/#{file_extension}",
      headers: api_headers,
      body: {
        phone: phone,
        document: buffer,
        fileName: attachment.file.filename.to_s,
        caption: message.outgoing_content,
        **params
      }.compact.to_json
    )

    raise ProviderUnavailableError unless process_response(response)

    response.parsed_response&.dig('messageId')
  end

  def send_video_message(phone, message, buffer, **params)
    response = HTTParty.post(
      "#{api_instance_path_with_token}/send-video",
      headers: api_headers,
      body: {
        phone: phone,
        video: buffer,
        caption: message.outgoing_content,
        **params
      }.compact.to_json
    )

    raise ProviderUnavailableError unless process_response(response)

    response.parsed_response&.dig('messageId')
  end

  def send_reaction_message(phone, message, **params)
    response = HTTParty.post(
      "#{api_instance_path_with_token}/send-reaction",
      headers: api_headers,
      body: {
        phone: phone,
        reaction: message.outgoing_content,
        messageId: message.in_reply_to_external_id,
        **params
      }.compact.to_json
    )

    raise ProviderUnavailableError unless process_response(response)

    response.parsed_response&.dig('messageId')
  end
end
