using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MarcusLaw.OutlookAddin.Core.Models;
using Serilog;

namespace MarcusLaw.OutlookAddin.Core.Services
{
    public sealed class AttachmentSaveService : IAttachmentSaveService
    {
        private readonly IEspoCrmClient _client;
        private readonly ILogger _logger;

        public AttachmentSaveService(IEspoCrmClient client, ILogger logger)
        {
            _client = client ?? throw new ArgumentNullException(nameof(client));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public async Task<AttachmentSaveSummary> SaveAsync(
            IReadOnlyList<EspoAttachment> attachments,
            string targetFolderPath,
            CancellationToken cancellationToken = default)
        {
            if (attachments == null) throw new ArgumentNullException(nameof(attachments));
            if (string.IsNullOrWhiteSpace(targetFolderPath)) throw new ArgumentException("targetFolderPath required", nameof(targetFolderPath));

            var summary = new AttachmentSaveSummary();
            if (attachments.Count == 0) return summary;

            var trimmedFolder = targetFolderPath.TrimEnd('/');

            foreach (var att in attachments)
            {
                cancellationToken.ThrowIfCancellationRequested();

                if (summary.AuthRequired)
                {
                    summary.Items.Add(new AttachmentSaveItemResult
                    {
                        FileName = att.Name,
                        Outcome = AttachmentSaveOutcome.AuthRequired,
                        ErrorMessage = "Skipped — earlier upload rejected by EspoCRM"
                    });
                    continue;
                }

                try
                {
                    // Step 1: create the EspoCRM Attachment (binary stored
                    // in data/upload/<id>; not yet on the network share).
                    var uploaded = await _client.UploadAttachmentAsync(
                        att.Name, att.ContentType, att.ContentBase64, cancellationToken).ConfigureAwait(false);

                    // Step 2: ask Marcus-Law's NetworkStorageIntegration
                    // to place the uploaded blob under <targetFolderPath>.
                    // The integration appends the filename to the path.
                    await _client.UploadAttachmentToNetworkStorageAsync(
                        uploaded.Id, trimmedFolder, cancellationToken).ConfigureAwait(false);

                    // Step 3: verify the file actually landed on the share —
                    // the server can return 200 yet leave nothing on disk
                    // when a downstream WebDAV proxy silently fails. Read
                    // the folder back and look for our filename.
                    await VerifyArrivedAsync(att.Name, trimmedFolder, cancellationToken).ConfigureAwait(false);

                    _logger.Information(
                        "Saved attachment {Name} → {Path} (attachmentId={AttachmentId})",
                        att.Name, trimmedFolder, uploaded.Id);

                    summary.Items.Add(new AttachmentSaveItemResult
                    {
                        FileName = att.Name,
                        Outcome = AttachmentSaveOutcome.Saved,
                        AttachmentId = uploaded.Id
                    });
                }
                catch (EspoCrmAuthorizationException ex)
                {
                    _logger.Warning(ex, "Attachment save aborted: EspoCRM rejected credentials");
                    summary.AuthRequired = true;
                    summary.Items.Add(new AttachmentSaveItemResult
                    {
                        FileName = att.Name,
                        Outcome = AttachmentSaveOutcome.AuthRequired,
                        ErrorMessage = ex.Message
                    });
                }
                catch (OperationCanceledException)
                {
                    throw;
                }
                catch (Exception ex)
                {
                    _logger.Warning(ex, "Attachment save failed for {Name}", att.Name);
                    summary.Items.Add(new AttachmentSaveItemResult
                    {
                        FileName = att.Name,
                        Outcome = AttachmentSaveOutcome.Failed,
                        ErrorMessage = ex.Message
                    });
                }
            }

            return summary;
        }

        /// <summary>
        /// Post-upload sanity check: list the folder we just wrote into
        /// and confirm a file with the same (possibly sanitized) name
        /// shows up. Diagnostic only — does not change the outcome. If
        /// the file isn't there, log a WARNING with everything that IS
        /// in the folder so we can spot the actual server-side path.
        /// </summary>
        private async Task VerifyArrivedAsync(string expectedName, string folderPath, CancellationToken ct)
        {
            try
            {
                var items = await _client.ListNetworkStorageFolderAsync(folderPath, ct).ConfigureAwait(false);
                var trimmedExpected = expectedName.Trim().TrimStart('-', ' ').TrimEnd('-', ' ');
                bool found = false;
                foreach (var it in items)
                {
                    if (it.IsFolder) continue;
                    if (string.Equals(it.Name, expectedName, StringComparison.Ordinal) ||
                        string.Equals(it.Name, trimmedExpected, StringComparison.Ordinal))
                    {
                        found = true;
                        break;
                    }
                }
                if (!found)
                {
                    var names = new List<string>();
                    foreach (var it in items)
                    {
                        if (it.IsFolder) continue;
                        names.Add(it.Name);
                    }
                    _logger.Warning(
                        "Post-upload verify: expected '{Expected}' under '{Path}' but it's NOT in the listing. " +
                        "Server reported the upload as successful but the file is not on the share. " +
                        "Files currently in that folder: [{Files}]",
                        expectedName, folderPath, string.Join(", ", names));
                }
                else
                {
                    _logger.Information("Post-upload verify: '{Expected}' present under '{Path}'", expectedName, folderPath);
                }
            }
            catch (Exception ex)
            {
                _logger.Warning(ex, "Post-upload verify failed for '{Expected}' under '{Path}'", expectedName, folderPath);
            }
        }
    }
}
