using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using MarcusLaw.OutlookAddin.Core.Models;
using Serilog;

namespace MarcusLaw.OutlookAddin.Core.Services
{
    public sealed class DiskRetryQueue : IRetryQueue
    {
        private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
        {
            WriteIndented = false,
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            PropertyNameCaseInsensitive = true
        };

        private static readonly UTF8Encoding Utf8NoBom = new UTF8Encoding(false);

        private readonly string _directory;
        private readonly ILogger _logger;

        public DiskRetryQueue(ILogger logger)
            : this(DefaultDirectory(), logger)
        {
        }

        public DiskRetryQueue(string directory, ILogger logger)
        {
            if (string.IsNullOrWhiteSpace(directory)) throw new ArgumentException("directory required", nameof(directory));
            _directory = directory;
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public string Directory => _directory;

        public Task<string> EnqueueAsync(RetryQueueItem item, CancellationToken cancellationToken = default)
        {
            if (item == null) throw new ArgumentNullException(nameof(item));

            EnsureDirectory();

            if (string.IsNullOrEmpty(item.Id))
            {
                item.Id = Guid.NewGuid().ToString("N");
            }
            if (item.EnqueuedAt == default)
            {
                item.EnqueuedAt = DateTimeOffset.UtcNow;
            }

            var finalPath = Path.Combine(_directory, item.Id + ".json");
            var tempPath = finalPath + ".tmp";

            var json = JsonSerializer.Serialize(item, JsonOptions);
            File.WriteAllText(tempPath, json, Utf8NoBom);

            if (File.Exists(finalPath))
            {
                File.Delete(finalPath);
            }
            File.Move(tempPath, finalPath);

            _logger.Information("Retry queue enqueued {Id} (messageId={MessageId})", item.Id, item.Request?.MessageId);
            return Task.FromResult(item.Id);
        }

        public Task<IReadOnlyList<RetryQueueItem>> ListAllAsync(CancellationToken cancellationToken = default)
        {
            var result = new List<RetryQueueItem>();
            if (!System.IO.Directory.Exists(_directory))
            {
                return Task.FromResult<IReadOnlyList<RetryQueueItem>>(result);
            }

            foreach (var path in System.IO.Directory.EnumerateFiles(_directory, "*.json"))
            {
                cancellationToken.ThrowIfCancellationRequested();
                try
                {
                    var json = File.ReadAllText(path, Encoding.UTF8);
                    var item = JsonSerializer.Deserialize<RetryQueueItem>(json, JsonOptions);
                    if (item != null)
                    {
                        if (string.IsNullOrEmpty(item.Id))
                        {
                            item.Id = Path.GetFileNameWithoutExtension(path);
                        }
                        result.Add(item);
                    }
                }
                catch (JsonException ex)
                {
                    _logger.Warning(ex, "Retry queue: skipping corrupt file {Path}", path);
                }
                catch (IOException ex)
                {
                    _logger.Warning(ex, "Retry queue: cannot read {Path}", path);
                }
            }

            return Task.FromResult<IReadOnlyList<RetryQueueItem>>(result);
        }

        public Task RemoveAsync(string id, CancellationToken cancellationToken = default)
        {
            if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException("id required", nameof(id));
            var path = Path.Combine(_directory, id + ".json");
            if (File.Exists(path))
            {
                File.Delete(path);
                _logger.Information("Retry queue removed {Id}", id);
            }
            return Task.CompletedTask;
        }

        private void EnsureDirectory()
        {
            if (!System.IO.Directory.Exists(_directory))
            {
                System.IO.Directory.CreateDirectory(_directory);
            }
        }

        private static string DefaultDirectory()
        {
            return Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                "MarcusLaw",
                "OutlookAddin",
                "retry-queue");
        }
    }
}
