using Microsoft.Extensions.Options;

namespace PlotLine.Services;

public sealed class StorageSettings
{
    public string? UploadsRoot { get; set; }
}

public interface IUploadStorageService
{
    string UploadsRootPath { get; }
    string GetDirectory(params string[] relativeSegments);
    string GetFilePath(params string[] relativeSegments);
    string GetPublicPath(params string[] relativeSegments);
    string? TryResolvePublicPath(string? storagePath, string? requiredPrefix = null);
    bool TryDeleteFile(string? storagePath, string? requiredPrefix = null);
}

public sealed class UploadStorageService : IUploadStorageService
{
    private const string PublicRoot = "uploads";
    private readonly StringComparison pathComparison;

    public UploadStorageService(IOptions<StorageSettings> options, IWebHostEnvironment environment)
    {
        pathComparison = OperatingSystem.IsWindows()
            ? StringComparison.OrdinalIgnoreCase
            : StringComparison.Ordinal;

        var configuredRoot = options.Value.UploadsRoot;
        UploadsRootPath = string.IsNullOrWhiteSpace(configuredRoot)
            ? Path.Combine(environment.WebRootPath, PublicRoot)
            : GetConfiguredRoot(environment.ContentRootPath, configuredRoot);

        UploadsRootPath = Path.GetFullPath(UploadsRootPath);
        Directory.CreateDirectory(UploadsRootPath);
    }

    public string UploadsRootPath { get; }

    public string GetDirectory(params string[] relativeSegments)
    {
        var directory = GetPhysicalPath(relativeSegments);
        Directory.CreateDirectory(directory);
        return directory;
    }

    public string GetFilePath(params string[] relativeSegments)
        => GetPhysicalPath(relativeSegments);

    public string GetPublicPath(params string[] relativeSegments)
    {
        var safeSegments = relativeSegments.SelectMany(SplitSegments).ToArray();
        return "/" + string.Join('/', new[] { PublicRoot }.Concat(safeSegments));
    }

    public string? TryResolvePublicPath(string? storagePath, string? requiredPrefix = null)
    {
        if (string.IsNullOrWhiteSpace(storagePath)
            || storagePath.StartsWith("http://", StringComparison.OrdinalIgnoreCase)
            || storagePath.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
        {
            return null;
        }

        var normalised = storagePath.Replace('\\', '/').TrimStart('/');
        if (!normalised.StartsWith(PublicRoot + "/", StringComparison.OrdinalIgnoreCase))
        {
            return null;
        }

        if (!string.IsNullOrWhiteSpace(requiredPrefix))
        {
            var normalisedPrefix = requiredPrefix.Replace('\\', '/').Trim('/');
            if (!normalised.StartsWith(normalisedPrefix + "/", StringComparison.OrdinalIgnoreCase))
            {
                return null;
            }
        }

        try
        {
            var relativePath = normalised[(PublicRoot.Length + 1)..];
            return GetPhysicalPath(SplitSegments(relativePath).ToArray());
        }
        catch (InvalidOperationException)
        {
            return null;
        }
    }

    public bool TryDeleteFile(string? storagePath, string? requiredPrefix = null)
    {
        var absolutePath = TryResolvePublicPath(storagePath, requiredPrefix);
        if (absolutePath is null || !File.Exists(absolutePath))
        {
            return false;
        }

        File.Delete(absolutePath);
        return true;
    }

    private string GetPhysicalPath(params string[] relativeSegments)
    {
        var safeSegments = relativeSegments.SelectMany(SplitSegments).ToArray();
        var combined = safeSegments.Aggregate(UploadsRootPath, Path.Combine);
        var fullPath = Path.GetFullPath(combined);
        return IsWithinUploadsRoot(fullPath) ? fullPath : throw new InvalidOperationException("Upload path escaped the configured uploads root.");
    }

    private bool IsWithinUploadsRoot(string path)
    {
        var root = Path.EndsInDirectorySeparator(UploadsRootPath)
            ? UploadsRootPath
            : UploadsRootPath + Path.DirectorySeparatorChar;

        return path.StartsWith(root, pathComparison) || string.Equals(path, UploadsRootPath, pathComparison);
    }

    private static string GetConfiguredRoot(string contentRootPath, string configuredRoot)
    {
        var expandedRoot = Environment.ExpandEnvironmentVariables(configuredRoot.Trim());
        return Path.IsPathRooted(expandedRoot)
            ? expandedRoot
            : Path.Combine(contentRootPath, expandedRoot);
    }

    private static IEnumerable<string> SplitSegments(string value)
    {
        foreach (var segment in value.Split(['/', '\\'], StringSplitOptions.RemoveEmptyEntries))
        {
            if (segment is "." or ".." || Path.IsPathRooted(segment) || segment.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
            {
                throw new InvalidOperationException("Invalid upload path segment.");
            }

            yield return segment;
        }
    }
}
