using Microsoft.EntityFrameworkCore;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.SecuredWrites;
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;

namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Repositories;

/// <summary>
/// EF Core implementation of <see cref="ISecuredWriteRepository"/> over the central
/// <c>PendingSecuredWrites</c> table. Mirrors the
/// <c>SiteCallAuditRepository</c> data-access shape: plain tracked EF reads/writes
/// against the shared <see cref="ScadaBridgeDbContext"/>, no raw SQL needed.
/// </summary>
public class SecuredWriteRepository : ISecuredWriteRepository
{
    private readonly ScadaBridgeDbContext _context;

    /// <summary>
    /// Initializes a new instance of the <see cref="SecuredWriteRepository"/> class.
    /// </summary>
    /// <param name="context">The EF Core database context.</param>
    public SecuredWriteRepository(ScadaBridgeDbContext context)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
    }

    /// <inheritdoc />
    public async Task<long> AddAsync(PendingSecuredWrite securedWrite, CancellationToken ct = default)
    {
        ArgumentNullException.ThrowIfNull(securedWrite);

        await _context.Set<PendingSecuredWrite>().AddAsync(securedWrite, ct);
        await _context.SaveChangesAsync(ct);
        return securedWrite.Id;
    }

    /// <inheritdoc />
    public async Task<PendingSecuredWrite?> GetAsync(long id, CancellationToken ct = default)
    {
        return await _context.Set<PendingSecuredWrite>()
            .FindAsync(new object?[] { id }, ct);
    }

    /// <inheritdoc />
    public async Task UpdateAsync(PendingSecuredWrite securedWrite, CancellationToken ct = default)
    {
        ArgumentNullException.ThrowIfNull(securedWrite);

        // The caller hands back the same tracked instance it read; Update covers the
        // detached case too (re-attaches and marks every column modified).
        _context.Set<PendingSecuredWrite>().Update(securedWrite);
        await _context.SaveChangesAsync(ct);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<PendingSecuredWrite>> QueryAsync(
        string? status,
        string? siteId,
        int skip,
        int take,
        IReadOnlyCollection<string>? permittedSiteIds = null,
        CancellationToken ct = default)
    {
        IQueryable<PendingSecuredWrite> query = _context.Set<PendingSecuredWrite>()
            .AsNoTracking();

        if (!string.IsNullOrWhiteSpace(status))
        {
            query = query.Where(p => p.Status == status);
        }

        if (!string.IsNullOrWhiteSpace(siteId))
        {
            query = query.Where(p => p.SiteId == siteId);
        }

        if (permittedSiteIds is not null)
        {
            query = query.Where(p => permittedSiteIds.Contains(p.SiteId));
        }

        return await query
            .OrderByDescending(p => p.SubmittedAtUtc)
            .ThenByDescending(p => p.Id)
            .Skip(skip)
            .Take(take)
            .ToListAsync(ct);
    }

    /// <inheritdoc />
    public async Task<bool> TryMarkApprovedAsync(
        long id,
        string verifierUser,
        string? verifierComment,
        DateTime decidedAtUtc,
        CancellationToken ct = default)
    {
        // Single-statement compare-and-swap: the conditional WHERE Status='Pending'
        // makes the Pending->Approved transition atomic at the row level, so two
        // verifiers approving concurrently produce exactly one rowsAffected==1 (the
        // winner) and one rowsAffected==0 (the loser). Parameterised via
        // ExecuteSqlInterpolatedAsync — same raw-SQL conditional-update pattern as
        // SiteCallAuditRepository's upsert-on-newer-status path.
        var rowsAffected = await _context.Database.ExecuteSqlInterpolatedAsync(
            $@"UPDATE dbo.PendingSecuredWrites
SET Status = 'Approved',
    VerifierUser = {verifierUser},
    VerifierComment = {verifierComment},
    DecidedAtUtc = {decidedAtUtc}
WHERE Id = {id}
  AND Status = 'Pending';",
            ct);

        return rowsAffected == 1;
    }

    /// <inheritdoc />
    public async Task<bool> TryMarkExpiredAsync(long id, DateTime expiredAtUtc, CancellationToken ct = default)
    {
        // Single-statement compare-and-swap: the conditional WHERE Status='Pending'
        // makes the Pending->Expired transition atomic at the row level, so two nodes
        // sweeping the same overdue write concurrently produce exactly one
        // rowsAffected==1 (the winner) and one rowsAffected==0 (the loser). Expiry is
        // a system decision — VerifierUser is left NULL. Same conditional-update
        // pattern as TryMarkApprovedAsync.
        var rowsAffected = await _context.Database.ExecuteSqlInterpolatedAsync(
            $@"UPDATE dbo.PendingSecuredWrites
SET Status = 'Expired',
    DecidedAtUtc = {expiredAtUtc}
WHERE Id = {id}
  AND Status = 'Pending';",
            ct);

        return rowsAffected == 1;
    }

    /// <inheritdoc />
    public async Task<int> CountAsync(
        string? status,
        string? siteId,
        IReadOnlyCollection<string>? permittedSiteIds = null,
        CancellationToken ct = default)
    {
        IQueryable<PendingSecuredWrite> query = _context.Set<PendingSecuredWrite>()
            .AsNoTracking();

        if (!string.IsNullOrWhiteSpace(status))
        {
            query = query.Where(p => p.Status == status);
        }

        if (!string.IsNullOrWhiteSpace(siteId))
        {
            query = query.Where(p => p.SiteId == siteId);
        }

        if (permittedSiteIds is not null)
        {
            query = query.Where(p => permittedSiteIds.Contains(p.SiteId));
        }

        return await query.CountAsync(ct);
    }
}
