using System.Text.Json;
using foodmarket.Domain.Integrations;
using foodmarket.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace foodmarket.Infrastructure.Integrations.MoySklad;

// Алиас на enum для обратной совместимости с кодом, использующим
// MoySklad.ImportJobStatus. Внутренне маппится на Domain.Integrations.
public enum ImportJobStatus { Running, Succeeded, Failed, Cancelled }

/// <summary>In-memory snapshot persisted <see cref="ImportJob"/> row.
/// Background-таски мутируют поля; периодически (и обязательно в finally)
/// контроллер вызывает <c>SaveAsync</c> чтобы сбросить состояние в БД.
///
/// Раньше всё жило в ConcurrentDictionary (TD-5): рестарт процесса терял
/// прогресс и историю. Теперь это снимок строки БД с тем же API, что был —
/// чтобы не трогать MoySkladImportService.</summary>
public class ImportJobProgress
{
    public Guid Id { get; init; } = Guid.NewGuid();
    public string Kind { get; init; } = "";
    public DateTime StartedAt { get; init; } = DateTime.UtcNow;
    public DateTime? FinishedAt { get; set; }
    public ImportJobStatus Status { get; set; } = ImportJobStatus.Running;
    public string? Stage { get; set; }
    public int Total { get; set; }
    public int Created { get; set; }
    public int Updated { get; set; }
    public int Skipped { get; set; }
    public int Deleted { get; set; }
    public int GroupsCreated { get; set; }
    public string? Message { get; set; }
    public List<string> Errors { get; set; } = new();
}

/// <summary>Persistence-backed реестр прогресса фоновых импортов. Singleton,
/// каждый метод открывает scope для свежего <see cref="AppDbContext"/> с
/// текущим tenant'ом (для background задач — через HttpContextTenantContext.UseOverride).
///
/// API совместимое со старой in-memory версией: Create/Get/RecentlyFinished
/// плюс новый <c>SaveAsync</c> — сейвить snapshot в БД на checkpoint'ах
/// (рекомендуется вызывать как минимум раз в финальном finally-блоке).</summary>
public class ImportJobRegistry
{
    private readonly IServiceScopeFactory _scopes;

    public ImportJobRegistry(IServiceScopeFactory scopes)
    {
        _scopes = scopes;
    }

    public ImportJobProgress Create(string kind)
    {
        var snap = new ImportJobProgress { Kind = kind };
        using var scope = _scopes.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        db.SkipAudit = true;
        db.Set<ImportJob>().Add(ToEntity(snap));
        db.SaveChanges();
        return snap;
    }

    public async Task SaveAsync(ImportJobProgress snap, CancellationToken ct = default)
    {
        using var scope = _scopes.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        db.SkipAudit = true;
        var existing = await db.Set<ImportJob>().FirstOrDefaultAsync(j => j.Id == snap.Id, ct);
        if (existing is null)
        {
            db.Set<ImportJob>().Add(ToEntity(snap));
        }
        else
        {
            existing.Kind = snap.Kind;
            existing.StartedAt = snap.StartedAt;
            existing.FinishedAt = snap.FinishedAt;
            existing.Status = (Domain.Integrations.ImportJobStatus)snap.Status;
            existing.Stage = snap.Stage;
            existing.Total = snap.Total;
            existing.Created = snap.Created;
            existing.Updated = snap.Updated;
            existing.Skipped = snap.Skipped;
            existing.Deleted = snap.Deleted;
            existing.GroupsCreated = snap.GroupsCreated;
            existing.Message = snap.Message;
            existing.ErrorsJson = JsonSerializer.Serialize(snap.Errors);
        }
        await db.SaveChangesAsync(ct);
    }

    public ImportJobProgress? Get(Guid id)
    {
        using var scope = _scopes.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var row = db.Set<ImportJob>().AsNoTracking().FirstOrDefault(j => j.Id == id);
        return row is null ? null : FromEntity(row);
    }

    public IReadOnlyList<ImportJobProgress> RecentlyFinished(int take = 10)
    {
        using var scope = _scopes.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var rows = db.Set<ImportJob>().AsNoTracking()
            .Where(j => j.FinishedAt != null)
            .OrderByDescending(j => j.FinishedAt)
            .Take(take)
            .ToList();
        return rows.Select(FromEntity).ToList();
    }

    private static ImportJob ToEntity(ImportJobProgress s) => new()
    {
        Id = s.Id,
        Kind = s.Kind,
        StartedAt = s.StartedAt,
        FinishedAt = s.FinishedAt,
        Status = (Domain.Integrations.ImportJobStatus)s.Status,
        Stage = s.Stage,
        Total = s.Total, Created = s.Created, Updated = s.Updated,
        Skipped = s.Skipped, Deleted = s.Deleted, GroupsCreated = s.GroupsCreated,
        Message = s.Message,
        ErrorsJson = JsonSerializer.Serialize(s.Errors),
    };

    private static ImportJobProgress FromEntity(ImportJob e) => new()
    {
        Id = e.Id,
        Kind = e.Kind,
        StartedAt = e.StartedAt,
        FinishedAt = e.FinishedAt,
        Status = (ImportJobStatus)(int)e.Status,
        Stage = e.Stage,
        Total = e.Total, Created = e.Created, Updated = e.Updated,
        Skipped = e.Skipped, Deleted = e.Deleted, GroupsCreated = e.GroupsCreated,
        Message = e.Message,
        Errors = string.IsNullOrEmpty(e.ErrorsJson) ? new()
               : (JsonSerializer.Deserialize<List<string>>(e.ErrorsJson) ?? new()),
    };
}
