/*
 * MinIO .NET Library for Amazon S3 Compatible Cloud Storage, (C) 2017-2021 MinIO, Inc.
 *
 * Licensed 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.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text.RegularExpressions;
using Minio.DataModel.ObjectLock;
using Minio.Helper;

namespace Minio.DataModel;

public class ObjectStat
{
    private ObjectStat()
    {
        MetaData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        ExtraHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
    }

    public string ObjectName { get; private set; }
    public long Size { get; private set; }
    public DateTime LastModified { get; private set; }
    public string ETag { get; private set; }
    public string ContentType { get; private set; }

    [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation",
        Justification = "Needs to be concrete type for XML deserialization")]
    public Dictionary<string, string> MetaData { get; }

    public string VersionId { get; private set; }
    public bool DeleteMarker { get; private set; }

    [SuppressMessage("Design", "MA0016:Prefer returning collection abstraction instead of implementation",
        Justification = "Needs to be concrete type for XML deserialization")]
    public Dictionary<string, string> ExtraHeaders { get; }

    public uint? TaggingCount { get; private set; }
    public string ArchiveStatus { get; private set; }
    public DateTime? Expires { get; private set; }
    public string ReplicationStatus { get; }
    public ObjectRetentionMode? ObjectLockMode { get; private set; }
    public DateTime? ObjectLockRetainUntilDate { get; private set; }
    public bool? LegalHoldEnabled { get; private set; }

    public static ObjectStat FromResponseHeaders(string objectName, IDictionary<string, string> responseHeaders)
    {
        if (string.IsNullOrEmpty(objectName))
            throw new ArgumentNullException(nameof(objectName), "Name of an object cannot be empty");
        if (responseHeaders is null) throw new ArgumentNullException(nameof(responseHeaders));

        var objInfo = new ObjectStat { ObjectName = objectName };
        foreach (var paramName in responseHeaders.Keys)
        {
            var paramValue = responseHeaders[paramName];
            switch (paramName.ToLowerInvariant())
            {
                case "content-length":
                    objInfo.Size = long.Parse(paramValue, NumberStyles.Number, CultureInfo.InvariantCulture);
                    break;
                case "last-modified":
                    objInfo.LastModified = DateTime.Parse(paramValue, CultureInfo.InvariantCulture);
                    break;
                case "etag":
                    objInfo.ETag = paramValue.Replace("\"", string.Empty, StringComparison.OrdinalIgnoreCase);
                    break;
                case "content-type":
                    objInfo.ContentType = paramValue;
                    objInfo.MetaData["Content-Type"] = objInfo.ContentType;
                    break;
                case "x-amz-version-id":
                    objInfo.VersionId = paramValue;
                    break;
                case "x-amz-delete-marker":
                    objInfo.DeleteMarker = paramValue.Equals("true", StringComparison.OrdinalIgnoreCase);
                    break;
                case "x-amz-archive-status":
                    objInfo.ArchiveStatus = paramValue;
                    break;
                case "x-amz-tagging-count":
                    if (int.TryParse(paramValue, NumberStyles.Integer, CultureInfo.InvariantCulture,
                            out var tagCount) && tagCount >= 0)
                        objInfo.TaggingCount = (uint)tagCount;
                    break;
                case "x-amz-expiration":
                    // x-amz-expiration header includes the expiration date and the corresponding rule id.
                    var expirationResponse = paramValue.Trim();
                    var expiryDatePattern =
                        @"(Sun|Mon|Tue|Wed|Thu|Fri|Sat), \d{2} (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} [A-Z]+";
                    var expiryMatch = Regex.Match(expirationResponse, expiryDatePattern, RegexOptions.None,
                        TimeSpan.FromHours(1));
                    if (expiryMatch.Success)
                        objInfo.Expires = DateTime.Parse(expiryMatch.Value, CultureInfo.CurrentCulture);

                    break;
                case "x-amz-object-lock-mode":
                    if (!string.IsNullOrWhiteSpace(paramValue))
                        objInfo.ObjectLockMode = paramValue.Equals("governance", StringComparison.OrdinalIgnoreCase)
                            ? ObjectRetentionMode.GOVERNANCE
                            : ObjectRetentionMode.COMPLIANCE;

                    break;
                case "x-amz-object-lock-retain-until-date":
                    var lockUntilDate = paramValue;
                    if (!string.IsNullOrWhiteSpace(lockUntilDate))
                        objInfo.ObjectLockRetainUntilDate = DateTime.Parse(lockUntilDate, CultureInfo.CurrentCulture);

                    break;
                case "x-amz-object-lock-legal-hold":
                    var legalHoldON = paramValue.Trim();
                    if (!string.IsNullOrWhiteSpace(legalHoldON))
                        objInfo.LegalHoldEnabled = legalHoldON.Equals("on", StringComparison.OrdinalIgnoreCase);
                    break;
                default:
                    if (OperationsUtil.IsSupportedHeader(paramName))
                        objInfo.MetaData[paramName] = paramValue;
                    else if (paramName.StartsWith("x-amz-meta-", StringComparison.OrdinalIgnoreCase))
                        objInfo.MetaData[paramName["x-amz-meta-".Length..]] = paramValue;
                    else
                        objInfo.ExtraHeaders[paramName] = paramValue;
                    break;
            }
        }

        return objInfo;
    }

    public override string ToString()
    {
        var versionInfo = "VersionId(None)";
        var legalHold = "LegalHold(None)";
        var taggingCount = "Tagging-Count(0)";
        var expires = "Expiry(None)";
        var objectLockInfo = "ObjectLock(None)";
        var archiveStatus = "Archive Status(None)";
        var replicationStatus = "Replication Status(None)";
        if (!string.IsNullOrWhiteSpace(VersionId))
        {
            versionInfo = $"Version ID({VersionId})";
            if (DeleteMarker) versionInfo = $"Version ID({VersionId}, deleted)";
        }

        if (Expires is not null) expires = "Expiry(" + Utils.To8601String(Expires.Value) + ")";
        if (ObjectLockMode is not null)
        {
            objectLockInfo = "ObjectLock Mode(" +
                             (ObjectLockMode == ObjectRetentionMode.GOVERNANCE ? "GOVERNANCE" : "COMPLIANCE") + ")";
            objectLockInfo += " Retain Until Date(" + Utils.To8601String(ObjectLockRetainUntilDate.Value) + ")";
        }

        if (TaggingCount is not null) taggingCount = "Tagging-Count(" + TaggingCount.Value + ")";
        if (LegalHoldEnabled is not null)
            legalHold = "LegalHold(" + (LegalHoldEnabled.Value ? "Enabled" : "Disabled") + ")";
        if (!string.IsNullOrWhiteSpace(ReplicationStatus))
            replicationStatus = "Replication Status(" + ReplicationStatus + ")";
        if (!string.IsNullOrWhiteSpace(ArchiveStatus)) archiveStatus = "Archive Status(" + ArchiveStatus + ")";
        var lineTwo = $"{expires} {objectLockInfo} {legalHold} {taggingCount} {archiveStatus} {replicationStatus}";

        return
            $"{ObjectName} : {versionInfo} Size({Size}) LastModified({LastModified}) ETag({ETag}) Content-Type({ContentType})" +
            (string.IsNullOrWhiteSpace(lineTwo) ? "" : "\n" + lineTwo);
    }
}
