/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Org.Apache.Rocketmq
{
    public class Message
    {
        internal static readonly Regex TopicRegex = new Regex("^[%a-zA-Z0-9_-]+$");

        private Message(string topic, byte[] body, string tag, List<string> keys,
            Dictionary<string, string> properties, DateTime? deliveryTimestamp, string messageGroup,
            int? priority, string liteTopic)
        {
            Topic = topic;
            Tag = tag;
            Keys = keys;
            Body = body;
            Properties = properties;
            DeliveryTimestamp = deliveryTimestamp;
            MessageGroup = messageGroup;
            Priority = priority;
            LiteTopic = liteTopic;
        }

        internal Message(Message message)
        {
            Topic = message.Topic;
            Tag = message.Tag;
            Keys = message.Keys;
            Body = message.Body;
            Properties = message.Properties;
            MessageGroup = message.MessageGroup;
            DeliveryTimestamp = message.DeliveryTimestamp;
            Priority = message.Priority;
            LiteTopic = message.LiteTopic;
        }

        public string Topic { get; }

        public byte[] Body { get; }

        public string Tag { get; }

        public List<string> Keys { get; }
        public Dictionary<string, string> Properties { get; }

        public DateTime? DeliveryTimestamp { get; }

        public string MessageGroup { get; }

        /// <summary>
        /// Gets the lite topic for dynamic topic routing.
        /// Only applicable for LITE message type. Enables flexible message routing without pre-defining all topics.
        /// Cannot be used with: messageGroup, deliveryTimestamp, or priority.
        /// </summary>
        public string LiteTopic { get; }

        /// <summary>
        /// Gets the priority of the message.
        /// Only applicable for PRIORITY topic type. Higher values indicate higher priority.
        /// Priority must be >= 0 and cannot be used with: messageGroup, deliveryTimestamp, or liteTopic.
        /// </summary>
        public int? Priority { get; }

        public override string ToString()
        {
            return
                $"{nameof(Topic)}: {Topic}, {nameof(Tag)}: {Tag}, {nameof(Keys)}: {string.Join(", ", Keys)}, {nameof(Properties)}: " +
                $"{string.Join(", ", Properties.Select(kvp => kvp.ToString()))}, {nameof(DeliveryTimestamp)}: {DeliveryTimestamp}, {nameof(MessageGroup)}: " +
                $"{MessageGroup}, {nameof(Priority)}: {Priority}, {nameof(LiteTopic)}: {LiteTopic}";
        }

        public class Builder
        {
            private string _topic;
            private byte[] _body;
            private string _tag;
            private List<string> _keys = new List<string>();
            private readonly Dictionary<string, string> _properties = new Dictionary<string, string>();
            private DateTime? _deliveryTimestamp;
            private string _messageGroup;
            private int? _priority;
            private string _liteTopic;

            public Builder SetTopic(string topic)
            {
                Preconditions.CheckArgument(null != topic, "topic should not be null");
                Preconditions.CheckArgument(topic != null && TopicRegex.Match(topic).Success,
                    $"topic does not match the regex {TopicRegex}");
                _topic = topic;
                return this;
            }

            public Builder SetBody(byte[] body)
            {
                Preconditions.CheckArgument(null != body && body.Length > 0, "body should not be empty");
                _body = body;
                return this;
            }

            public Builder SetTag(string tag)
            {
                Preconditions.CheckArgument(!string.IsNullOrWhiteSpace(tag), "tag should not be null or white space");
                Preconditions.CheckArgument(tag != null && !tag.Contains("|"), "tag should not contain \"|\"");
                _tag = tag;
                return this;
            }

            public Builder SetKeys(params string[] keys)
            {
                _keys = new List<string>();
                foreach (var key in keys)
                {
                    Preconditions.CheckArgument(!string.IsNullOrWhiteSpace(key),
                        "key should not be null or white space");
                    _keys.Add(key);
                }

                return this;
            }

            public Builder AddProperty(string key, string value)
            {
                Preconditions.CheckArgument(!string.IsNullOrWhiteSpace(key),
                    "key should not be null or white space");
                Preconditions.CheckArgument(!string.IsNullOrWhiteSpace(value),
                    "value should not be null or white space");
                _properties[key!] = value;
                return this;
            }

            public Builder SetDeliveryTimestamp(DateTime deliveryTimestamp)
            {
                Preconditions.CheckArgument(null == _messageGroup,
                    "deliveryTimestamp and messageGroup should not be set at same time");
                Preconditions.CheckArgument(!_priority.HasValue,
                    "deliveryTimestamp and priority should not be set at same time");
                Preconditions.CheckArgument(null == _liteTopic,
                    "deliveryTimestamp and liteTopic should not be set at same time");
                _deliveryTimestamp = DateTimeKind.Utc == deliveryTimestamp.Kind
                    ? TimeZoneInfo.ConvertTimeFromUtc(deliveryTimestamp, TimeZoneInfo.Local)
                    : deliveryTimestamp;
                return this;
            }

            public Builder SetMessageGroup(string messageGroup)
            {
                Preconditions.CheckArgument(!string.IsNullOrWhiteSpace(messageGroup),
                    "messageGroup should not be null or white space");
                Preconditions.CheckArgument(null == _deliveryTimestamp,
                    "messageGroup and deliveryTimestamp should not be set at same time");
                Preconditions.CheckArgument(!_priority.HasValue,
                    "messageGroup and priority should not be set at same time");
                Preconditions.CheckArgument(null == _liteTopic,
                    "messageGroup and liteTopic should not be set at same time");
                _messageGroup = messageGroup;
                return this;
            }

            /// <summary>
            /// Sets the lite topic for lite message.
            /// LiteTopic enables dynamic topic routing without pre-defining all topics.
            /// Cannot be used together with: messageGroup, deliveryTimestamp, or priority.
            /// </summary>
            /// <param name="liteTopic">The lite topic name for dynamic routing.</param>
            /// <returns>The builder instance for method chaining.</returns>
            /// <exception cref="ArgumentException">Thrown when liteTopic is empty or conflicts with other message properties.</exception>
            public Builder SetLiteTopic(string liteTopic)
            {
                Preconditions.CheckArgument(null == _deliveryTimestamp,
                    "liteTopic and deliveryTimestamp should not be set at same time");
                Preconditions.CheckArgument(null == _messageGroup,
                    "liteTopic and messageGroup should not be set at same time");
                Preconditions.CheckArgument(null == _priority,
                    "liteTopic and priority should not be set at same time");
                Preconditions.CheckArgument(!string.IsNullOrWhiteSpace(liteTopic),
                    "liteTopic should not be null or white space");
                _liteTopic = liteTopic;
                return this;
            }

            /// <summary>
            /// Sets the priority for priority message.
            /// Priority must be >= 0 (higher value = higher priority).
            /// Cannot be used together with: messageGroup, deliveryTimestamp, or liteTopic.
            /// </summary>
            /// <param name="priority">The priority level. Higher values indicate higher priority.</param>
            /// <returns>The builder instance for method chaining.</returns>
            /// <exception cref="ArgumentException">Thrown when priority is negative or conflicts with other message properties.</exception>
            public Builder SetPriority(int priority)
            {
                Preconditions.CheckArgument(priority >= 0,
                    "priority must be greater than or equal to 0");
                Preconditions.CheckArgument(null == _deliveryTimestamp,
                    "priority and deliveryTimestamp should not be set at same time");
                Preconditions.CheckArgument(null == _messageGroup,
                    "priority and messageGroup should not be set at same time");
                Preconditions.CheckArgument(null == _liteTopic,
                    "priority and liteTopic should not be set at same time");
                _priority = priority;
                return this;
            }

            public Message Build()
            {
                Preconditions.CheckArgument(null != _topic, "topic has not been set yet");
                Preconditions.CheckArgument(null != _body, "body has not been set yet");
                return new Message(_topic, _body, _tag, _keys, _properties, _deliveryTimestamp, _messageGroup,
                    _priority, _liteTopic);
            }
        }
    }
}