# frozen_string_literal: true

require 'aws-sdk-pinpoint'
require 'aws-sdk-pinpointsmsvoice'
require 'forwardable'
require 'i18n'
require 'telephony/util'
require 'telephony/alert_sender'
require 'telephony/configuration'
require 'telephony/errors'
require 'telephony/otp_sender'
require 'telephony/phone_number_info'
require 'telephony/response'
require 'telephony/test/call'
require 'telephony/test/message'
require 'telephony/test/error_simulator'
require 'telephony/test/sms_sender'
require 'telephony/test/voice_sender'
require 'telephony/pinpoint/aws_credential_builder'
require 'telephony/pinpoint/pinpoint_helper'
require 'telephony/pinpoint/opt_out_manager'
require 'telephony/pinpoint/sms_sender'
require 'telephony/pinpoint/voice_sender'

module Telephony
  # GSM 03.38 character set
  # https://docs.aws.amazon.com/pinpoint/latest/userguide/channels-sms-limitations-characters.html
  GSM_NON_WHITE_SPACE_CHARACTERS = %w[
    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e
    f g h i j k l m n o p q r s t u v w x y z à Å å Ä ä Ç É é è ì Ñ ñ ò Ø ø Ö ö ù Ü ü Æ æ ß 0 1 2 3
    4 5 6 7 8 9 & * @ : , ¤ $ = ! > # - ¡ ¿ ( < % . + £ ? " ) § ; ' / _ ¥ Δ Φ Γ Λ Ω Π Ψ Σ Θ Ξ
  ].to_set.freeze
  GSM_WHITESPACE_CHARACTERS = ["\n", "\r", ' '].to_set.freeze
  GSM_DOUBLE_CHARACTERS = ['^', '{', '}', '\\', '[', ']', '~', '|', '€'].to_set.freeze
  GSM_CHARACTERS = (GSM_NON_WHITE_SPACE_CHARACTERS + GSM_WHITESPACE_CHARACTERS +
                   GSM_DOUBLE_CHARACTERS).freeze

  UCS_2_BASIC_CHAR_MAX = 0xFFFF
  LOG_FILENAME = 'telephony.log'

  extend SingleForwardable

  def self.config
    @config ||= Configuration.new
    yield @config if block_given?
    @config
  end

  def self.send_authentication_otp(to:, otp:, expiration:, otp_format:,
                                   channel:, domain:, country_code:, extra_metadata:)
    OtpSender.new(
      to: to,
      otp: otp,
      expiration: expiration,
      otp_format: otp_format,
      channel: channel,
      domain: domain,
      country_code: country_code,
      extra_metadata: extra_metadata,
    ).send_authentication_otp
  end

  def self.send_confirmation_otp(to:, otp:, expiration:, otp_format:, otp_length:,
                                 channel:, domain:, country_code:, extra_metadata:)
    OtpSender.new(
      to: to,
      otp: otp,
      expiration: expiration,
      otp_format: otp_format,
      otp_length: otp_length,
      channel: channel,
      domain: domain,
      country_code: country_code,
      extra_metadata: extra_metadata,
    ).send_confirmation_otp
  end

  def self.alert_sender
    AlertSender.new
  end

  def_delegators :alert_sender,
                 :send_doc_auth_link,
                 :send_personal_key_regeneration_notice,
                 :send_personal_key_sign_in_notice,
                 :send_account_deleted_notice,
                 :send_account_reset_notice,
                 :send_account_reset_cancellation_notice,
                 :send_dupe_profile_sign_in_attempted_notice,
                 :send_dupe_profile_created_notice,
                 :send_notification

  # @param [String] phone_number phone number in E.164 format
  # @return [PhoneNumberInfo] info about the phone number
  def self.phone_info(phone_number)
    sender = case Telephony.config.adapter
    when :pinpoint
      Pinpoint::SmsSender.new
    when :test
      Test::SmsSender.new
    end

    sender.phone_info(phone_number)
  end

  def self.log_info(event:)
    event[:log_filename] = LOG_FILENAME
    self.config.logger.info(event.to_json)
  end

  def self.log_warn(event:)
    event[:log_filename] = LOG_FILENAME
    self.config.logger.warn(event.to_json)
  end

  # A character in a GSM 03.38 message counts as one character, unless it is explicitly one of
  # the characters that requires an escape sequence, which makes it count as two characters.
  #
  # Messages that contain non-GSM 03.38 characters are encoded as UCS-2 with 2-byte characters.
  # Codepoints less than 0xFFFF can be represented as one character, but other codepoints are
  # encoded as two.
  #
  # This method does not handle message length added for multi-part message headers.
  def self.sms_character_length(text)
    if gsm_chars_only?(text)
      text.chars.sum do |character|
        if GSM_DOUBLE_CHARACTERS.include?(character)
          2
        else
          1
        end
      end
    else
      text.chars.sum do |char|
        char.codepoints.sum do |codepoint|
          if codepoint <= UCS_2_BASIC_CHAR_MAX
            1
          else
            2
          end
        end
      end
    end
  end

  # A single GSM 03.38 message can contain up to 160 characters. If the length is beyond that,
  # the message is split into parts containing 153 characters each. The capacity is lower because
  # messages must now also encode information about message order.
  #
  # UCS-2 messages behave similarly, but the size is limited to 70 and 67 characters respectively.
  def self.sms_parts(text)
    length = sms_character_length(text)

    if gsm_chars_only?(text)
      gsm_parts(length)
    else
      non_gsm_parts(length)
    end
  end

  def self.gsm_chars_only?(text)
    text.chars.all? { |x| GSM_CHARACTERS.include?(x) }
  end

  def self.gsm_parts(length)
    if length <= 160
      1
    else
      (length / 153.0).ceil
    end
  end

  def self.non_gsm_parts(length)
    if length <= 70
      1
    else
      (length / 67.0).ceil
    end
  end

  private_class_method :gsm_parts, :non_gsm_parts
end
