using C4IT.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace C4IT_IAM_Engine
{
    public static class Helper
    {
        public const int MaxAdGroupNameLength = 64;
        public const int MaxAdGroupDescriptionLength = 1024;
        public const int MaxAdGroupLoopDigits = 3;
        public const string DefaultGroupNameSanitizeReplacement = "_";
        private const string AdUnsafeGroupNameCharactersPattern = @"[\x00-\x1F\x7F\/\\\[\]:;\|=,\+\*\?<>]";
        private const int MinLeadingRelativePathSegmentLength = 3;
        private const int MinSingleLeadingRelativePathSegmentLength = 2;
        private const int MinLastRelativePathSegmentLength = 12;

        public sealed class BoundedTemplateContext
        {
            public string[] SanitizedSegments { get; set; } = Array.Empty<string>();
            public string FolderName { get; set; } = string.Empty;
            public bool WasShortened { get; set; }
            public string OriginalValue { get; set; } = string.Empty;
            public string FinalValue { get; set; } = string.Empty;
            public string Strategy { get; set; } = string.Empty;
        }

        public sealed class RootPathTemplateContext
        {
            public string Server { get; set; } = string.Empty;
            public string[] Segments { get; set; } = Array.Empty<string>();
            public string Name { get; set; } = string.Empty;
            public string Path { get; set; } = string.Empty;
            public string PathSegmentSeparator { get; set; } = DefaultGroupNameSanitizeReplacement;
        }

        public static string ReplaceLoopTag(this string str, int loop)
        {
            return Regex.Replace(str, @"(?<loopTag>{{(?<prefix>[^}]*)(?<loop>LOOP)(?<postfix>[^{]*)}})", loop <= 0 ? "" : "${prefix}" + loop + "${postfix}");
        }
        public static string ReplaceTags(this string str, IDictionary<string, string> dict)
        {
            if (str.Equals(string.Empty) || str == null || dict == null || dict.Count == 0)
                return str;
            return dict.Aggregate(str, (current, value) =>
                        current.Replace("{{" + value.Key + "}}", value.Value));
        }
        public static string ApplyTemplatePlaceholders(string templateValue, bool allowRelativePath, string defaultRelativePath, string[] sanitizedSegments, string folderName)
        {
            return ApplyTemplatePlaceholders(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName, null, DefaultGroupNameSanitizeReplacement);
        }

        public static string ApplyTemplatePlaceholders(string templateValue, bool allowRelativePath, string defaultRelativePath, string[] sanitizedSegments, string folderName, RootPathTemplateContext rootContext)
        {
            return ApplyTemplatePlaceholders(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName, rootContext, DefaultGroupNameSanitizeReplacement);
        }

        public static string ApplyTemplatePlaceholders(string templateValue, bool allowRelativePath, string defaultRelativePath, string[] sanitizedSegments, string folderName, RootPathTemplateContext rootContext, string pathSegmentSeparator)
        {
            if (templateValue == null)
                return string.Empty;

            var result = Regex.Replace(templateValue, @"{{\s*NAME\s*}}", folderName ?? string.Empty, RegexOptions.IgnoreCase);
            result = ApplyRootPathPlaceholders(result, rootContext);

            if (allowRelativePath)
            {
                result = Regex.Replace(result, @"{{\s*RELATIVEPATH(?:\s*\(\s*(\d+)\s*\))?\s*}}", match =>
                {
                    if (sanitizedSegments == null || sanitizedSegments.Length == 0)
                        return string.Empty;

                    if (!match.Groups[1].Success)
                        return defaultRelativePath;

                    if (!int.TryParse(match.Groups[1].Value, out var segmentIndex) || segmentIndex < 0)
                        return defaultRelativePath;

                    var segmentCount = Math.Min(sanitizedSegments.Length, segmentIndex + 1);
                    var skip = sanitizedSegments.Length - segmentCount;
                    return JoinSanitizedPathSegments(sanitizedSegments.Skip(skip), pathSegmentSeparator);
                }, RegexOptions.IgnoreCase);
            }

            return result;
        }

        public static RootPathTemplateContext GetRootPathTemplateContext(string rootPath)
        {
            return GetRootPathTemplateContext(rootPath, DefaultGroupNameSanitizeReplacement);
        }

        public static RootPathTemplateContext GetRootPathTemplateContext(string rootPath, string groupNameSanitizeReplacement)
        {
            var segments = SplitPathSegments(rootPath);
            if (segments.Length == 0)
                return new RootPathTemplateContext();

            var isUncPath = (rootPath ?? string.Empty).Trim().Replace('/', '\\').StartsWith(@"\\", StringComparison.Ordinal);
            var server = isUncPath ? SanitizePathSegment(segments[0], groupNameSanitizeReplacement) : string.Empty;
            var pathSegments = isUncPath ? segments.Skip(1).ToArray() : segments;
            var sanitizedPathSegments = pathSegments.Select(i => SanitizePathSegment(i, groupNameSanitizeReplacement)).ToArray();

            return new RootPathTemplateContext
            {
                Server = server,
                Segments = sanitizedPathSegments,
                Name = sanitizedPathSegments.Length == 0 ? string.Empty : sanitizedPathSegments[sanitizedPathSegments.Length - 1],
                Path = sanitizedPathSegments.Length == 0 ? string.Empty : JoinSanitizedPathSegments(sanitizedPathSegments, groupNameSanitizeReplacement),
                PathSegmentSeparator = NormalizeGroupNameSanitizeReplacement(groupNameSanitizeReplacement)
            };
        }

        public static BoundedTemplateContext GetBoundedAdGroupTemplateContext(
            string templateValue,
            bool allowRelativePath,
            string defaultRelativePath,
            string[] sanitizedSegments,
            string folderName,
            IDictionary<string, string> replacementTags,
            int maxLength,
            string logContext,
            string valueLabel = "AD-Gruppenname",
            RootPathTemplateContext rootContext = null,
            bool preserveCase = false,
            string pathSegmentSeparator = DefaultGroupNameSanitizeReplacement)
        {
            var effectiveSegments = (sanitizedSegments ?? Array.Empty<string>()).Where(i => i != null).ToArray();
            var effectiveFolderName = folderName ?? string.Empty;
            var currentRelativePath = GetCurrentRelativePath(effectiveSegments, defaultRelativePath, pathSegmentSeparator);
            var originalValue = MaterializeTemplateValue(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags, rootContext, preserveCase, pathSegmentSeparator);
            var measuredValue = MaterializeTemplateValueForLength(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags, rootContext, preserveCase, pathSegmentSeparator);
            var usesRelativePath = allowRelativePath && Regex.IsMatch(templateValue ?? string.Empty, @"{{\s*RELATIVEPATH", RegexOptions.IgnoreCase);
            var usesName = Regex.IsMatch(templateValue ?? string.Empty, @"{{\s*NAME\s*}}", RegexOptions.IgnoreCase);
            var strategy = string.Empty;

            while (measuredValue.Length > maxLength)
            {
                var changed = false;

                if (usesRelativePath && TryShortenRelativePath(ref effectiveSegments))
                {
                    effectiveFolderName = effectiveSegments.Length > 0 ? effectiveSegments[effectiveSegments.Length - 1] : string.Empty;
                    if (string.IsNullOrWhiteSpace(strategy))
                        strategy = "truncate-relativepath";
                    changed = true;
                }
                else if (usesName && !usesRelativePath && TryShortenName(ref effectiveFolderName))
                {
                    if (string.IsNullOrWhiteSpace(strategy))
                        strategy = "truncate-name";
                    changed = true;
                }

                if (!changed)
                    break;

                currentRelativePath = GetCurrentRelativePath(effectiveSegments, defaultRelativePath, pathSegmentSeparator);
                originalValue = MaterializeTemplateValue(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags, rootContext, preserveCase, pathSegmentSeparator);
                measuredValue = MaterializeTemplateValueForLength(templateValue, allowRelativePath, currentRelativePath, effectiveSegments, effectiveFolderName, replacementTags, rootContext, preserveCase, pathSegmentSeparator);
            }

            var initialValue = MaterializeTemplateValue(
                templateValue,
                allowRelativePath,
                GetCurrentRelativePath(sanitizedSegments, defaultRelativePath, pathSegmentSeparator),
                sanitizedSegments,
                folderName,
                replacementTags,
                rootContext,
                preserveCase,
                pathSegmentSeparator);
            var result = new BoundedTemplateContext
            {
                SanitizedSegments = effectiveSegments,
                FolderName = effectiveSegments.Length > 0 ? effectiveSegments[effectiveSegments.Length - 1] : effectiveFolderName,
                OriginalValue = initialValue,
                FinalValue = originalValue,
                WasShortened = !string.Equals(initialValue, originalValue, StringComparison.Ordinal),
                Strategy = strategy
            };

            if (result.WasShortened)
            {
                cLogManager.DefaultLogger.LogEntry(
                    LogLevels.Warning,
                    $"{valueLabel} gekuerzt ({logContext}): '{result.OriginalValue}' ({GetMeasuredTemplateLength(result.OriginalValue)}) -> '{result.FinalValue}' ({GetMeasuredTemplateLength(result.FinalValue)}), Strategie: {result.Strategy}, Limit: {maxLength}.");
            }

            if (measuredValue.Length > maxLength)
            {
                cLogManager.DefaultLogger.LogEntry(
                    LogLevels.Warning,
                    $"{valueLabel} ueberschreitet weiterhin das sichere Limit ({logContext}): '{result.FinalValue}' ({measuredValue.Length}), Limit: {maxLength}.");
            }

            return result;
        }
        public static string SanitizePathSegment(string segment)
        {
            return SanitizePathSegment(segment, DefaultGroupNameSanitizeReplacement);
        }

        public static string SanitizePathSegment(string segment, string groupNameSanitizeReplacement)
        {
            if (string.IsNullOrEmpty(segment))
                return string.Empty;

            var replacement = NormalizeGroupNameSanitizeReplacement(groupNameSanitizeReplacement);
            return Regex.Replace(segment, AdUnsafeGroupNameCharactersPattern, match => replacement);
        }

        public static string NormalizeGroupNameSanitizeReplacement(string replacement)
        {
            if (replacement == null)
                return DefaultGroupNameSanitizeReplacement;

            var trimmed = replacement.Trim();
            if (trimmed.Equals("<empty>", StringComparison.OrdinalIgnoreCase)
                || trimmed.Equals("empty", StringComparison.OrdinalIgnoreCase)
                || trimmed.Equals("none", StringComparison.OrdinalIgnoreCase)
                || trimmed.Equals("remove", StringComparison.OrdinalIgnoreCase))
            {
                return string.Empty;
            }

            return trimmed;
        }

        public static string JoinSanitizedPathSegments(IEnumerable<string> sanitizedSegments, string groupNameSanitizeReplacement)
        {
            if (sanitizedSegments == null)
                return string.Empty;

            return string.Join(NormalizeGroupNameSanitizeReplacement(groupNameSanitizeReplacement), sanitizedSegments);
        }

        public static string ApplyAdGroupNameCasing(string value, bool preserveCase)
        {
            if (value == null)
                return string.Empty;

            return preserveCase ? value : value.ToUpper();
        }
        public static void CreatePathWithWriteAccess(string FilePath)
        {
            try
            {
                var PF = Environment.ExpandEnvironmentVariables(FilePath);
                Directory.CreateDirectory(PF);
            }
            catch { }
        }
        public static string MaskAllButLastAndFirst(this string input, char maskingChar = '*')
        {
            if (input.Length > 3)
            {
                var pattern = @"^(.{1})(.+)(.{1})$";
                var match = Regex.Match(input, pattern);
                var mask = new string(maskingChar, match.Groups[2].Length);
                return $"{match.Groups[1]}{mask}{match.Groups[3]}";
            }
            else
                return new string(maskingChar, input.Length);
        }

        private static string MaterializeTemplateValue(
            string templateValue,
            bool allowRelativePath,
            string defaultRelativePath,
            string[] sanitizedSegments,
            string folderName,
            IDictionary<string, string> replacementTags,
            RootPathTemplateContext rootContext,
            bool preserveCase,
            string pathSegmentSeparator)
        {
            var materializedValue = ApplyTemplatePlaceholders(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName, rootContext, pathSegmentSeparator)
                .ReplaceTags(replacementTags);

            return ApplyAdGroupNameCasing(materializedValue, preserveCase);
        }

        private static string MaterializeTemplateValueForLength(
            string templateValue,
            bool allowRelativePath,
            string defaultRelativePath,
            string[] sanitizedSegments,
            string folderName,
            IDictionary<string, string> replacementTags,
            RootPathTemplateContext rootContext,
            bool preserveCase,
            string pathSegmentSeparator)
        {
            return NormalizeLoopPlaceholderLength(
                MaterializeTemplateValue(templateValue, allowRelativePath, defaultRelativePath, sanitizedSegments, folderName, replacementTags, rootContext, preserveCase, pathSegmentSeparator));
        }

        private static string ApplyRootPathPlaceholders(string templateValue, RootPathTemplateContext rootContext)
        {
            if (templateValue == null)
                return string.Empty;

            var context = rootContext ?? new RootPathTemplateContext();
            var result = Regex.Replace(templateValue, @"{{\s*ROOT_SERVER\s*}}", context.Server ?? string.Empty, RegexOptions.IgnoreCase);
            result = Regex.Replace(result, @"{{\s*ROOT_NAME\s*}}", context.Name ?? string.Empty, RegexOptions.IgnoreCase);
            result = Regex.Replace(result, @"{{\s*ROOT_PATH(?:\s*\(\s*(\d+)\s*\))?\s*}}", match =>
            {
                var segments = context.Segments ?? Array.Empty<string>();
                if (!match.Groups[1].Success)
                    return context.Path ?? string.Empty;

                if (!int.TryParse(match.Groups[1].Value, out var segmentCount) || segmentCount <= 0)
                    return string.Empty;

                var take = Math.Min(segmentCount, segments.Length);
                return take == 0 ? string.Empty : JoinSanitizedPathSegments(segments.Skip(segments.Length - take), context.PathSegmentSeparator);
            }, RegexOptions.IgnoreCase);
            result = Regex.Replace(result, @"{{\s*ROOT_SEGMENT\s*\(\s*(\d+)\s*\)\s*}}", match =>
            {
                var segments = context.Segments ?? Array.Empty<string>();
                if (!int.TryParse(match.Groups[1].Value, out var segmentIndex) || segmentIndex < 0 || segmentIndex >= segments.Length)
                    return string.Empty;

                return segments[segmentIndex] ?? string.Empty;
            }, RegexOptions.IgnoreCase);

            return result;
        }

        private static string NormalizeLoopPlaceholderLength(string templateValue)
        {
            if (string.IsNullOrWhiteSpace(templateValue))
                return templateValue ?? string.Empty;

            return Regex.Replace(
                templateValue,
                @"{{(?<prefix>[^}]*)(?<loop>LOOP)(?<postfix>[^{]*)}}",
                match => match.Groups["prefix"].Value + new string('9', MaxAdGroupLoopDigits) + match.Groups["postfix"].Value,
                RegexOptions.IgnoreCase);
        }

        private static int GetMeasuredTemplateLength(string templateValue)
        {
            return NormalizeLoopPlaceholderLength(templateValue).Length;
        }

        private static string[] SplitPathSegments(string path)
        {
            return (path ?? string.Empty)
                .Trim()
                .Replace('/', '\\')
                .Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
        }

        private static string GetCurrentRelativePath(string[] sanitizedSegments, string fallbackRelativePath, string pathSegmentSeparator)
        {
            if (sanitizedSegments != null && sanitizedSegments.Length > 0)
                return JoinSanitizedPathSegments(sanitizedSegments, pathSegmentSeparator);

            return fallbackRelativePath ?? string.Empty;
        }

        private static int GetLoopReservationLength(string templateValue)
        {
            if (string.IsNullOrWhiteSpace(templateValue))
                return 0;

            var reservation = 0;
            foreach (Match match in Regex.Matches(templateValue, @"{{(?<prefix>[^}]*)(?<loop>LOOP)(?<postfix>[^{]*)}}", RegexOptions.IgnoreCase))
            {
                reservation += match.Groups["prefix"].Value.Length + MaxAdGroupLoopDigits + match.Groups["postfix"].Value.Length;
            }

            return reservation;
        }

        private static bool TryShortenRelativePath(ref string[] segments)
        {
            if (segments == null || segments.Length == 0)
                return false;

            var lastIndex = segments.Length - 1;
            if (segments.Length > 2)
            {
                var candidateIndex = -1;
                var candidateLength = MinLeadingRelativePathSegmentLength;

                for (var i = 0; i < lastIndex; i++)
                {
                    if (string.IsNullOrWhiteSpace(segments[i]) || segments[i].Length <= candidateLength)
                        continue;

                    candidateIndex = i;
                    candidateLength = segments[i].Length;
                }

                if (candidateIndex >= 0)
                {
                    segments[candidateIndex] = segments[candidateIndex].Substring(0, segments[candidateIndex].Length - 1);
                    return true;
                }

                segments = segments.Skip(1).ToArray();
                return true;
            }

            if (segments.Length == 2)
            {
                if (segments[0].Length > MinSingleLeadingRelativePathSegmentLength)
                {
                    segments[0] = segments[0].Substring(0, segments[0].Length - 1);
                    return true;
                }

                if (segments[lastIndex].Length > MinLastRelativePathSegmentLength)
                {
                    segments[lastIndex] = segments[lastIndex].Substring(0, segments[lastIndex].Length - 1);
                    return true;
                }

                if (segments[0].Length > 1)
                {
                    segments[0] = segments[0].Substring(0, segments[0].Length - 1);
                    return true;
                }

                if (segments[lastIndex].Length > 1)
                {
                    segments[lastIndex] = segments[lastIndex].Substring(0, segments[lastIndex].Length - 1);
                    return true;
                }

                return false;
            }

            if (segments[lastIndex].Length > 1)
            {
                segments[lastIndex] = segments[lastIndex].Substring(0, segments[lastIndex].Length - 1);
                return true;
            }

            return false;
        }

        private static bool TryShortenName(ref string folderName)
        {
            if (string.IsNullOrWhiteSpace(folderName) || folderName.Length <= 1)
                return false;

            folderName = folderName.Substring(0, folderName.Length - 1);
            return true;
        }
    }
}
