# frozen_string_literal: true

require "json"

module MessageEvent
	module Emittable
		def emit(redis, stream: "messages")
			args = to_redis_fields.flat_map { |k, v| [k, v] }
			redis.xadd(stream, "*", *args).catch do |e|
				puts "WARN Failed to emit message event: #{e.message}"
			end
		end
	end

	module ValidTel
		def self.===(value)
			value.to_s.match?(/\A\+1\d{10}\z/)
		end
	end

	class Base
		include Emittable

		def initialize(event:, timestamp: nil)
			raise ArgumentError, "event must be a String" unless event.is_a?(String)

			timestamp = Time.iso8601(timestamp) if timestamp.is_a?(String)
			@event = event
			@timestamp = timestamp
		end

		attr_reader :event, :timestamp

		# We use to_redis_fields instead of to_h because we want to serialize values
		# (e.g., Time -> ISO8601 string, arrays -> JSON). A plain to_h would return
		# raw Ruby objects, which is less useful for parsing from other
		# projects/languages.
		def to_redis_fields
			fields = {
				"event" => @event,
				"source" => "bwmsgsv2"
			}
			if @timestamp
				fields["timestamp"] = @timestamp.is_a?(Time) ? @timestamp.iso8601 : @timestamp
			end
			fields
		end
	end

	class Message < Base
		def initialize(to:, from:, body:, owner:, bandwidth_id: nil, media_urls: [], **kwargs)
			raise ArgumentError, "owner must be a valid US telephone number" unless ValidTel === owner
			raise ArgumentError, "to must be an array" unless to.is_a?(Array)
			raise ArgumentError, "body must be a String" unless body.is_a?(String)
			raise ArgumentError, "media_urls must be an Array" unless media_urls.is_a?(Array)
			if bandwidth_id && !bandwidth_id.is_a?(String)
				raise ArgumentError, "bandwidth_id must be a String"
			end

			@from = from
			@to = to
			@body = body
			@media_urls = media_urls
			@owner = owner
			@bandwidth_id = bandwidth_id if bandwidth_id
			super(**kwargs)
		end

		attr_reader :from, :to, :body, :media_urls

		def to_redis_fields
			fields = super.merge(
				"owner" => @owner,
				"from" => @from,
				"to" => JSON.dump(@to)
			)
			fields["body"] = @body unless @body.nil?
			fields["bandwidth_id"] = @bandwidth_id
			fields["media_urls"] = JSON.dump(@media_urls) unless @media_urls.empty?
			fields
		end
	end

	class In < Message
		def initialize(**kwargs)
			super(event: "in", **kwargs)
		end

		attr_reader :owner, :bandwidth_id
	end

	class Out < Message
		def initialize(stanza_id:, **kwargs)
			raise ArgumentError, "stanza_id must be a String" unless stanza_id.is_a?(String)

			@stanza_id = stanza_id
			super(event: "out", **kwargs)
		end

		attr_reader :stanza_id, :bandwidth_id

		def to_redis_fields
			super.merge("stanza_id" => @stanza_id)
		end
	end

	class Thru < Message
		def initialize(stanza_id:, **kwargs)
			raise ArgumentError, "stanza_id must be a String" unless stanza_id.is_a?(String)

			@stanza_id = stanza_id
			super(event: "thru", **kwargs)
		end

		attr_reader :stanza_id

		def to_redis_fields
			super.merge("stanza_id" => @stanza_id)
		end
	end

	class Delivered < Base
		def initialize(stanza_id:, bandwidth_id:, **kwargs)
			raise ArgumentError, "stanza_id must be a String" unless stanza_id.is_a?(String)
			raise ArgumentError, "bandwidth_id must be a String" unless bandwidth_id.is_a?(String)

			@stanza_id = stanza_id
			@bandwidth_id = bandwidth_id
			super(event: "delivered", **kwargs)
		end

		attr_reader :stanza_id, :bandwidth_id

		def to_redis_fields
			super.merge(
				"stanza_id" => @stanza_id,
				"bandwidth_id" => @bandwidth_id
			)
		end
	end

	class Failed < Base
		def initialize(stanza_id:, bandwidth_id:, error_code:, error_description:, **kwargs)
			raise ArgumentError, "stanza_id must be a String" unless stanza_id.is_a?(String)
			raise ArgumentError, "bandwidth_id must be a String" unless bandwidth_id.is_a?(String)
			raise ArgumentError, "error_code must be a String" unless error_code.is_a?(String)
			raise ArgumentError, "error_description must be a String" unless error_description.is_a?(String)

			@stanza_id = stanza_id
			@bandwidth_id = bandwidth_id
			@error_code = error_code
			@error_description = error_description
			super(event: "failed", **kwargs)
		end

		attr_reader :stanza_id, :bandwidth_id, :error_code, :error_description

		def to_redis_fields
			super.merge(
				"stanza_id" => @stanza_id,
				"bandwidth_id" => @bandwidth_id,
				"error_code" => @error_code,
				"error_description" => @error_description
			)
		end
	end
end
