using System.Text.RegularExpressions;

namespace Fast.Logic.FileService;

/// Interface for file sharding strategies that distribute files across subfolders.
public interface IShardingStrategy
{
    /// Calculates the shard folder name for a given file name.
    /// <param name="fileName">The file name to calculate sharding for.</param>
    /// <returns>The shard subfolder name, or empty string if no sharding applies.</returns>
    string GetShardFolderName(string fileName);

    /// Determines if a filename is a valid GUID format.
    /// <param name="fileName">The filename to check (with or without extension).</param>
    /// <returns>True if the filename (without extension) is a valid GUID.</returns>
    bool IsGuidFileName(string? fileName);

    /// Calculates the full sharded path for a file.
    /// <param name="basePath">The base folder path.</param>
    /// <param name="fileName">The file name.</param>
    /// <returns>The full path including the shard subfolder.</returns>
    string GetShardedPath(string basePath, string fileName);
}

/// Default sharding strategy that:
/// - Uses first 2 characters for GUID-named files (e.g., "ab" for "ab123456...")
/// - Uses ticket number ranges for ticket-prefixed files (e.g., "1000-1999" for GAS1234)
/// - Returns empty string for files that don't match either pattern
public class DefaultShardingStrategy : IShardingStrategy
{
    /// Regex pattern for matching ticket numbers.
    /// Looks for the last hyphen (with optional spaces), a known ticket prefix,
    /// and captures the subsequent number.
    private static readonly Regex TicketNumberRegex = new(
        @"\s*-\s*(?:GAS|OPT|BSW|FFS|STR|SSW|CTR|BRH|TFR|CRO|FUT)(\d+)",
        RegexOptions.RightToLeft | RegexOptions.Compiled);

    /// The size of each ticket number range bucket.
    private const int RangeSize = 1000;

    public string GetShardFolderName(string fileName)
    {
        if (string.IsNullOrWhiteSpace(fileName))
            return string.Empty;

        // GUID-based files: use first 2 characters
        if (IsGuidFileName(fileName))
            return fileName[..2].ToLowerInvariant();

        // Ticket-based files: use number range
        var match = TicketNumberRegex.Match(fileName);
        if (match.Success && int.TryParse(match.Groups[1].Value, out int number))
        {
            int rangeStart = (number / RangeSize) * RangeSize;
            return $"{rangeStart}-{rangeStart + RangeSize - 1}";
        }

        // No sharding pattern matched
        return string.Empty;
    }

    public bool IsGuidFileName(string? fileName)
    {
        if (string.IsNullOrWhiteSpace(fileName))
            return false;

        string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
        return Guid.TryParse(fileNameWithoutExtension, out _);
    }

    public string GetShardedPath(string basePath, string fileName)
    {
        var shardFolder = GetShardFolderName(fileName);
        if (string.IsNullOrEmpty(shardFolder))
            return Path.Combine(basePath, fileName);
        return Path.Combine(basePath, shardFolder, fileName);
    }
}

/// A no-op sharding strategy that places all files in the same folder.
public class NoShardingStrategy : IShardingStrategy
{
    public string GetShardFolderName(string fileName) => string.Empty;

    public bool IsGuidFileName(string? fileName) => false;

    public string GetShardedPath(string basePath, string fileName) => Path.Combine(basePath, fileName);
}

/// Extension methods for working with sharding strategies.
public static class ShardingExtensions
{
    /// Creates a sharded subdirectory on the local filesystem if it doesn't exist.
    /// <param name="strategy">The sharding strategy.</param>
    /// <param name="basePath">The base directory path.</param>
    /// <param name="fileName">The file name to calculate sharding for.</param>
    /// <param name="createIfNotExists">If true, creates the directory if it doesn't exist.</param>
    /// <returns>The shard folder name.</returns>
    public static string CreateShardedSubdirectory(this IShardingStrategy strategy, string basePath, string fileName, bool createIfNotExists = true)
    {
        var shardFolder = strategy.GetShardFolderName(fileName);

        if (!string.IsNullOrEmpty(shardFolder) && createIfNotExists)
        {
            var fullPath = Path.Combine(basePath, shardFolder);
            Directory.CreateDirectory(fullPath);
        }

        return shardFolder;
    }

    /// Combines multiple path segments for SharePoint.
    /// <param name="paths">The path segments to combine.</param>
    /// <returns>The combined path, normalized for SharePoint.</returns>
    public static string CombineSharePointPaths(params string?[] paths)
    {
        var nonEmptyPaths = paths
            .Where(p => !string.IsNullOrWhiteSpace(p))
            .Select(p => p!.Trim('/').Trim('\\'));
        return string.Join("/", nonEmptyPaths);
    }
}