using System;
using System.IO;
using System.Security.Cryptography;
using System.Net;
using System.Threading.Tasks;
using MaxLib.WebServer;
using System.Threading;

namespace Dacryptero.Web
{
    public class StorageService : WebService
    {
        public StorageService()
            : base(ServerStage.CreateDocument)
        {
        }

        public override bool CanWorkWith(WebProgressTask task)
        {
            var loc = task.Request.Location;
            return loc.StartsUrlWith(new[] { "storage" }) &&
                loc.DocumentPathTiles.Length == 4 &&
                (loc.DocumentPathTiles[1] == "show" || loc.DocumentPathTiles[1] == "download") &&
                loc.DocumentPathTiles[2].ToObjectId() is not null &&
                loc.GetParameter.TryGetValue("key", out _);
        }

        public override async Task ProgressTask(WebProgressTask task)
        {
            var session = await WebSocketConnection.FindFromSessionId(task.Request.Location.GetParameter["key"])
                .ConfigureAwait(false);
            if (session is null)
            {
                task.Response.FieldLocation = "/storage/invalid?reason=session&id="
                    + task.Request.Location.DocumentPathTiles[2] + "&name=" 
                    + WebUtility.UrlEncode(task.Request.Location.DocumentPathTiles[3]);
                task.Response.StatusCode = HttpStateCode.TemporaryRedirect;
                return;
            }

            var file = await session.DB.GetFileAsync(
                task.Request.Location.DocumentPathTiles[2].ToObjectId()!,
                CancellationToken.None
            ).ConfigureAwait(false);

            if (file is null)
            {
                task.Response.FieldLocation = "/storage/invalid?reason=file&id="
                    + task.Request.Location.DocumentPathTiles[2] + "&name=" 
                    + WebUtility.UrlEncode(task.Request.Location.DocumentPathTiles[3]);
                task.Response.StatusCode = HttpStateCode.TemporaryRedirect;
                return;
            }

            try
            {
                var fileEntry = file.Value.Value.Entries[file.Value.Value.Entries.Count - 1];

                if (task.Request.Location.GetParameter.TryGetValue("date", out string? dateStr)
                    && DateTime.TryParse(dateStr, out DateTime date)
                )
                {
                    fileEntry = file.Value.Value.Entries[0];
                    foreach (var entry in file.Value.Value.Entries)
                        if (entry.Date <= date && entry.Date > fileEntry.Date)
                            fileEntry = entry;
                }

                var stream = new FileStream(
                    Path.Combine(file.Value.Source.Config.DirectoryPath, fileEntry.Path),
                    FileMode.Open,
                    FileAccess.Read,
                    FileShare.Read
                );
                var aes = Aes.Create();
                aes.Key = fileEntry.StorageKey.ToArray();
                aes.IV =  fileEntry.StorageIV.ToArray();
                // var crypt = new CryptoStream(stream, aes.CreateDecryptor(fileEntry.StorageKey.ToArray(), fileEntry.StorageIV.ToArray()), CryptoStreamMode.Read);
                var crypt = new CryptoStream(stream, aes.CreateDecryptor(), CryptoStreamMode.Read);
                
                // Serilog.Log.Verbose("Encryption Key={key}", Convert.ToBase64String(fileEntry.StorageKey.Span));
                // Serilog.Log.Verbose("Encryption IV={keivy}", Convert.ToBase64String(fileEntry.StorageIV.Span));

                task.Document.DataSources.Add(
                    new MaxLib.WebServer.Chunked.HttpChunkedStream(crypt)
                    {
                        MimeType = file.Value.Value.Mime,
                    }
                );

                var disposition = task.Request.Location.DocumentPathTiles[1] switch
                {
                    "download" => "attachment",
                    _ => "inline"
                };
                task.Response.SetHeader(
                    "Content-Disposition",
                    $"{disposition}; filename*=UTF-8''{Uri.EscapeDataString(file.Value.Value.Name)}"
                );
            }
            catch (Exception e)
            {
                Serilog.Log.Error(e, "Cannot decrypt file");
            }
        }
    }
}