using System.IO.Compression;
using Microsoft.Extensions.Logging;
using MaksIT.Core.Extensions;
using MaksIT.CertsUI.Engine.Domain.Certs;
using MaksIT.CertsUI.Engine.Dto.Certs;
using MaksIT.CertsUI.Engine.Persistence.Services;
using MaksIT.Results;

namespace MaksIT.CertsUI.Engine.DomainServices;

/// <summary>
/// Registration cache use cases (load/save, zip import/export). Orchestrates <see cref="Persistence.Services.IRegistrationCachePersistenceService"/> only from the engine layer.
/// </summary>
public interface IRegistrationCacheDomainService {

  #region Read

  Task<Result<RegistrationCache[]?>> LoadAllAsync(CancellationToken cancellationToken = default);

  Task<Result<RegistrationCache?>> LoadAsync(Guid accountId, CancellationToken cancellationToken = default);

  #endregion

  #region Write

  Task<Result> SaveAsync(Guid accountId, RegistrationCache cache, CancellationToken cancellationToken = default);

  Task<Result> DeleteAllAsync(CancellationToken cancellationToken = default);

  Task<Result> DeleteAsync(Guid accountId, CancellationToken cancellationToken = default);

  #endregion

  #region Zip import/export

  Task<Result<byte[]?>> DownloadCacheZipAsync(CancellationToken cancellationToken = default);

  Task<Result<byte[]?>> DownloadAccountCacheZipAsync(Guid accountId, CancellationToken cancellationToken = default);

  Task<Result> UploadCacheZipAsync(byte[] zipBytes, CancellationToken cancellationToken = default);

  Task<Result> UploadAccountCacheZipAsync(Guid accountId, byte[] zipBytes, CancellationToken cancellationToken = default);

  #endregion
}

/// <summary>
/// Domain-level registration cache operations (zip + row persistence). Host <see cref="MaksIT.CertsUI.Services.CacheService"/> delegates here.
/// </summary>
public sealed class RegistrationCacheDomainService(
  ILogger<RegistrationCacheDomainService> logger,
  IRegistrationCachePersistenceService registrationCachePersistence
) : IRegistrationCacheDomainService {

  #region Read

  public Task<Result<RegistrationCache[]?>> LoadAllAsync(CancellationToken cancellationToken = default) =>
    registrationCachePersistence.LoadAllAsync(cancellationToken);

  public Task<Result<RegistrationCache?>> LoadAsync(Guid accountId, CancellationToken cancellationToken = default) =>
    registrationCachePersistence.LoadAsync(accountId, cancellationToken);

  #endregion

  #region Write

  public Task<Result> SaveAsync(Guid accountId, RegistrationCache cache, CancellationToken cancellationToken = default) =>
    registrationCachePersistence.SaveAsync(accountId, cache, cancellationToken);

  public Task<Result> DeleteAllAsync(CancellationToken cancellationToken = default) =>
    registrationCachePersistence.DeleteAllAsync(cancellationToken);

  public Task<Result> DeleteAsync(Guid accountId, CancellationToken cancellationToken = default) =>
    registrationCachePersistence.DeleteAsync(accountId, cancellationToken);

  #endregion

  #region Zip import/export

  public async Task<Result<byte[]?>> DownloadCacheZipAsync(CancellationToken cancellationToken = default) {
    try {
      var allResult = await registrationCachePersistence.LoadAllAsync(cancellationToken).ConfigureAwait(false);
      if (!allResult.IsSuccess || allResult.Value == null)
        return Result<byte[]?>.InternalServerError(null, allResult.Messages?.ToArray() ?? ["Could not load registration caches."]);

      var rows = allResult.Value;
      using var ms = new MemoryStream();
      if (rows.Length == 0) {
        using (new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true)) { }
        return Result<byte[]?>.Ok(ms.ToArray());
      }
      using (var zip = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true)) {
        foreach (var row in rows) {
          var entry = zip.CreateEntry($"{row.AccountId}.json");
          using var entryStream = entry.Open();
          using var writer = new StreamWriter(entryStream);
          writer.Write(row.ToJson());
        }
      }
      var zipBytes = ms.ToArray();
      logger.LogInformation("Exported {Count} registration caches to zip.", rows.Length);
      return Result<byte[]?>.Ok(zipBytes);
    }
    catch (Exception ex) {
      var message = "Error creating registration cache zip.";
      logger.LogError(ex, message);
      return Result<byte[]?>.InternalServerError(null, [message, .. ex.ExtractMessages()]);
    }
  }

  public async Task<Result<byte[]?>> DownloadAccountCacheZipAsync(Guid accountId, CancellationToken cancellationToken = default) {
    try {
      var readResult = await registrationCachePersistence.LoadAsync(accountId, cancellationToken).ConfigureAwait(false);
      if (!readResult.IsSuccess || readResult.Value == null) {
        var message = $"Registration cache not found for account {accountId}.";
        logger.LogWarning(message);
        return Result<byte[]?>.NotFound(null, message);
      }
      var row = readResult.Value;
      using var ms = new MemoryStream();
      using (var zip = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true)) {
        var entry = zip.CreateEntry($"{accountId}.json");
        using var entryStream = entry.Open();
        using var writer = new StreamWriter(entryStream);
        writer.Write(row.ToJson());
      }
      var zipBytes = ms.ToArray();
      logger.LogInformation("Account registration cache zipped for {AccountId}", accountId);
      return Result<byte[]?>.Ok(zipBytes);
    }
    catch (Exception ex) {
      var message = "Error creating account registration cache zip.";
      logger.LogError(ex, message);
      return Result<byte[]?>.InternalServerError(null, [message, .. ex.ExtractMessages()]);
    }
  }

  public Task<Result> UploadCacheZipAsync(byte[] zipBytes, CancellationToken cancellationToken = default) {
    try {
      using var ms = new MemoryStream(zipBytes);
      using var zip = new ZipArchive(ms, ZipArchiveMode.Read);
      return ImportZipEntriesAsync(zip.Entries, null, cancellationToken);
    }
    catch (Exception ex) {
      var message = "Error reading or importing registration cache zip.";
      logger.LogError(ex, message);
      return Task.FromResult(Result.InternalServerError([message, .. ex.ExtractMessages()]));
    }
  }

  public async Task<Result> UploadAccountCacheZipAsync(Guid accountId, byte[] zipBytes, CancellationToken cancellationToken = default) {
    try {
      using var ms = new MemoryStream(zipBytes);
      using var zip = new ZipArchive(ms, ZipArchiveMode.Read);
      var import = await ImportZipEntriesAsync(zip.Entries, accountId, cancellationToken).ConfigureAwait(false);
      if (!import.IsSuccess)
        return import;

      return Result.Ok(import.Messages?.ToArray() ?? ["Imported account registration cache zip."]);
    }
    catch (Exception ex) {
      var message = "Error reading or importing account registration cache zip.";
      logger.LogError(ex, message);
      return Result.InternalServerError([message, .. ex.ExtractMessages()]);
    }
  }

  #endregion

  #region Zip import helpers

  private async Task<Result> ImportZipEntriesAsync(IReadOnlyList<ZipArchiveEntry> entries, Guid? enforcedAccountId, CancellationToken cancellationToken) {
    var processedJsonEntries = 0;

    foreach (var entry in entries) {
      cancellationToken.ThrowIfCancellationRequested();
      if (string.IsNullOrEmpty(entry.Name) || !entry.Name.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
        continue;

      processedJsonEntries++;

      var name = Path.GetFileNameWithoutExtension(entry.Name);
      if (!Guid.TryParse(name, out var id))
        return Result.BadRequest($"Invalid cache entry name '{entry.Name}'. Expected '<accountId>.json'.");

      if (enforcedAccountId != null && id != enforcedAccountId.Value)
        return Result.BadRequest($"Account upload accepts only '{enforcedAccountId}'. Found '{id}' in '{entry.Name}'.");

      using var stream = entry.Open();
      using var reader = new StreamReader(stream);
      var json = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
      if (string.IsNullOrWhiteSpace(json))
        return Result.BadRequest($"Cache entry '{entry.Name}' is empty.");

      var payload = json.ToObject<RegistrationCachePayloadDocument>();
      if (payload == null) {
        logger.LogWarning("Skipping zip entry {Name}: invalid JSON.", entry.FullName);
        return Result.BadRequest($"Cache entry '{entry.Name}' has invalid JSON.");
      }

      var cache = new RegistrationCache {
        Id = payload.Id?.ToNullableGuid() ?? payload.AccountId,
        AccountId = payload.AccountId,
        Description = payload.Description ?? "",
        Contacts = payload.Contacts ?? [],
        IsStaging = payload.IsStaging,
        ChallengeType = payload.ChallengeType ?? "",
        IsDisabled = payload.IsDisabled,
        AccountKey = payload.AccountKey,
        Key = payload.Key,
        Location = payload.Location,
        CachedCerts = payload.CachedCerts,
        AcmeRenewalNotBeforeUtcByHostname = payload.AcmeRenewalNotBeforeUtcByHostname
      };

      var save = await registrationCachePersistence.SaveAsync(id, cache, cancellationToken).ConfigureAwait(false);
      if (!save.IsSuccess)
        return save;
    }

    if (enforcedAccountId != null && processedJsonEntries != 1)
      return Result.BadRequest($"Account upload requires exactly one JSON entry named '{enforcedAccountId}.json'. Found {processedJsonEntries}.");

    var message = $"Imported registration caches from zip ({processedJsonEntries} entries).";
    logger.LogInformation(message);
    return Result.Ok(message);
  }

  #endregion
}
