# frozen_string_literal: true

require "json"
require "value_semantics"

module MessageEvent
	module Emittable
		def emit(redis=LazyObject.new { 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)
			return value.match?(/\A\+1\d{10}\z/) if value.is_a?(String)
			return value.to_s.match?(/\A\+1\d{10}\z/) if value.respond_to?(:to_s)

			false
		end
	end

	class MessageFields
		include ValueSemantics.for_attributes {
			event String
			timestamp Time, coerce: true
			from ValidTel
			to ArrayOf(ValidTel)
			message_id String
			body String
			media_urls ArrayOf(String), coerce: true
		}

		def self.coerce_timestamp(value)
			value.is_a?(String) ? Time.parse(value) : value
		end

		def self.coerce_media_urls(value)
			value.is_a?(String) ? JSON.parse(value) : value
		end

		def to_redis_fields
			{
				"event" => event,
				"timestamp" => timestamp.iso8601,
				"from" => from,
				"to" => JSON.dump(to),
				"message_id" => message_id,
				"has_media" => media_urls.any?.to_s,
				"body" => body,
				"media_urls" => JSON.dump(media_urls)
			}
		end
	end

	%w[In Out Thru].each { |n|
		const_set(n, Class.new(MessageFields) {
			include Emittable

			const_set(n, lambda { |v|
				v == n.downcase
			})

			define_method(:initialize) { |**a|
				super(**a.merge(event: n.downcase))
			}
		})
	}

	class Delivered
		include Emittable
		Delivered = lambda { |v| v == "delivered" }
		include ValueSemantics.for_attributes {
			event Delivered
			timestamp Either(Time, String)
			message_id String
		}

		def initialize(**a)
			super(**a.merge(event: "delivered"))
		end

		def to_redis_fields = {
			"event" => event,
			"timestamp" => timestamp.iso8601,
			"message_id" => message_id
		}
	end

	class Failed
		include Emittable
		Failed = lambda { |v| v == "failed" }
		include ValueSemantics.for_attributes {
			event Failed
			timestamp Either(Time, String)
			message_id String
			error_code String
			error_description String
		}

		def initialize(**a)
			super(**a.merge(event: "failed"))
		end

		def to_redis_fields = {
			"event" => event,
			"timestamp" => timestamp.is_a?(Time) ? timestamp.iso8601 : timestamp,
			"message_id" => message_id,
			"error_code" => error_code,
			"error_description" => error_description
		}
	end
end
