# frozen_string_literal: true

require "json"
require "time"

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

	def self.strip_prefix(tel)
		tel.to_s.sub(/\Asms:/, "").strip
	end

	module ValidTel
		def self.===(value)
			match?(value)
		end

		def self.match?(value)
			value.to_s.match?(/\A\+?\d+\Z/)
		end
	end

	module NanpaTel
		def self.===(value)
			match?(value)
		end

		def self.match?(value)
			value.to_s.match?(/\A\+1\d{10}\Z/)
		end
	end

	class Base
		include Emittable

		@validators = [
			lambda {
				unless @event.is_a?(String)
					raise ArgumentError, "event must be a String"
				end
			}
		]

		def self.validators
			parent = superclass.respond_to?(:validators) ? superclass.validators : []
			parent + (@validators || [])
		end

		def initialize(event:, timestamp: nil)
			timestamp = Time.iso8601(timestamp) if timestamp.is_a?(String)
			@event = event
			@timestamp = timestamp
			self.class.validators.each { |validator| instance_exec(&validator) }
		end

		attr_reader :event, :timestamp

		def to_redis_fields
			fields = {
				"event" => @event,
				"source" => "endstream"
			}
			if @timestamp
				fields["timestamp"] =
					@timestamp.is_a?(Time) ? @timestamp.iso8601 : @timestamp
			end
			fields
		end
	end

	class Message < Base
		@validators = [
			lambda {
				unless ValidTel.match?(@owner)
					raise(
						ArgumentError,
						"owner must be a number, optionally prefixed with +"
					)
				end
			},
			lambda {
				raise ArgumentError, "to must be an array" unless @to.is_a?(Array)
			},
			lambda {
				unless @to.all? { |t| ValidTel.match?(t) }
					raise(
						ArgumentError,
						"to must be a number, optionally prefixed with +"
					)
				end
			},
			lambda {
				unless @body.nil? || @body.is_a?(String)
					raise ArgumentError, "body must be a String or nil"
				end
			},
			lambda {
				unless @media_urls.is_a?(Array)
					raise ArgumentError, "media_urls must be an Array"
				end
			}
		]

		def initialize(to:, from:, body:, owner:, media_urls: [], **kwargs)
			owner = MessageEvent.strip_prefix(owner)
			from = MessageEvent.strip_prefix(from)
			to = to.map { |t| MessageEvent.strip_prefix(t) }

			@from = from
			@to = to
			@body = body
			@media_urls = media_urls
			@owner = owner
			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["media_urls"] = JSON.dump(@media_urls)
			fields
		end
	end

	class In < Message
		@validators = [
			lambda {
				unless @endstream_id.is_a?(String)
					raise ArgumentError, "endstream_id must be a String"
				end
			}
		]

		def initialize(endstream_id:, **kwargs)
			@endstream_id = endstream_id
			super(event: "in", **kwargs)
		end

		attr_reader :owner, :endstream_id

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

	class Failed < Base
		@validators = [
			lambda {
				unless @endstream_id.is_a?(String)
					raise ArgumentError, "endstream_id must be a String"
				end
			},
			lambda {
				unless @error_code.is_a?(String)
					raise ArgumentError, "error_code must be a String"
				end
			},
			lambda {
				unless @error_description.is_a?(String)
					raise ArgumentError, "error_description must be a String"
				end
			}
		]

		def initialize(endstream_id:, error_code:, error_description:, **kwargs)
			@endstream_id = endstream_id
			@error_code = error_code
			@error_description = error_description
			super(event: "failed", **kwargs)
		end

		attr_reader :endstream_id, :error_code, :error_description

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

	class Out < Message
		@validators = [
			lambda {
				unless @stanza_id.is_a?(String)
					raise ArgumentError, "stanza_id must be a String"
				end
			}
		]

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

		attr_reader :stanza_id

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