using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using ScadaLink.Commons.Entities.InboundApi;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.Commons.Types.InboundApi;

namespace ScadaLink.ConfigurationDatabase.Repositories;

public class InboundApiRepository : IInboundApiRepository
{
    private readonly ScadaLinkDbContext _context;
    private readonly ILogger<InboundApiRepository> _logger;

    public InboundApiRepository(ScadaLinkDbContext context, ILogger<InboundApiRepository>? logger = null)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
        _logger = logger ?? NullLogger<InboundApiRepository>.Instance;
    }

    public async Task<ApiKey?> GetApiKeyByIdAsync(int id, CancellationToken cancellationToken = default)
        => await _context.Set<ApiKey>().FindAsync(new object[] { id }, cancellationToken);

    public async Task<IReadOnlyList<ApiKey>> GetAllApiKeysAsync(CancellationToken cancellationToken = default)
        => await _context.Set<ApiKey>().ToListAsync(cancellationToken);

    /// <summary>
    /// ConfigurationDatabase-012: API keys are persisted only as a deterministic hash,
    /// never as plaintext, so this lookup hashes the supplied plaintext value and
    /// matches it against the stored <see cref="ApiKey.KeyHash"/> column. The
    /// unpeppered default hasher is used here because the repository has no access to
    /// the deployment pepper; the inbound-API authentication path does not use this
    /// method — it loads all keys and compares hashes constant-time in
    /// <c>ApiKeyValidator</c> with the configured (peppered) hasher.
    /// </summary>
    public async Task<ApiKey?> GetApiKeyByValueAsync(string keyValue, CancellationToken cancellationToken = default)
    {
        var keyHash = ApiKeyHasher.Default.Hash(keyValue);
        return await _context.Set<ApiKey>().FirstOrDefaultAsync(k => k.KeyHash == keyHash, cancellationToken);
    }

    public async Task AddApiKeyAsync(ApiKey apiKey, CancellationToken cancellationToken = default)
        => await _context.Set<ApiKey>().AddAsync(apiKey, cancellationToken);

    public Task UpdateApiKeyAsync(ApiKey apiKey, CancellationToken cancellationToken = default)
    { _context.Set<ApiKey>().Update(apiKey); return Task.CompletedTask; }

    public async Task DeleteApiKeyAsync(int id, CancellationToken cancellationToken = default)
    {
        var entity = await GetApiKeyByIdAsync(id, cancellationToken);
        if (entity != null) _context.Set<ApiKey>().Remove(entity);
    }

    public async Task<ApiMethod?> GetApiMethodByIdAsync(int id, CancellationToken cancellationToken = default)
        => await _context.Set<ApiMethod>().FindAsync(new object[] { id }, cancellationToken);

    public async Task<IReadOnlyList<ApiMethod>> GetAllApiMethodsAsync(CancellationToken cancellationToken = default)
        => await _context.Set<ApiMethod>().ToListAsync(cancellationToken);

    public async Task<ApiMethod?> GetMethodByNameAsync(string name, CancellationToken cancellationToken = default)
        => await _context.Set<ApiMethod>().FirstOrDefaultAsync(m => m.Name == name, cancellationToken);

    public async Task<IReadOnlyList<ApiKey>> GetApprovedKeysForMethodAsync(int methodId, CancellationToken cancellationToken = default)
    {
        var method = await _context.Set<ApiMethod>().FindAsync(new object[] { methodId }, cancellationToken);
        if (method?.ApprovedApiKeyIds == null)
            return new List<ApiKey>();

        // ApprovedApiKeyIds is a comma-separated string of integer ApiKey ids. A token that
        // fails to parse indicates a corrupt value: it is dropped (it cannot identify a key),
        // but the corruption is logged as a warning so it is observable rather than silent.
        // A corrupt list would otherwise quietly approve fewer keys than intended.
        var keyIds = new List<int>();
        foreach (var token in method.ApprovedApiKeyIds.Split(',', StringSplitOptions.RemoveEmptyEntries))
        {
            var trimmed = token.Trim();
            if (int.TryParse(trimmed, out var id) && id > 0)
            {
                keyIds.Add(id);
            }
            else
            {
                _logger.LogWarning(
                    "ApiMethod {MethodId} has a malformed approved-API-key id token '{Token}' " +
                    "in ApprovedApiKeyIds; it was dropped. The method may approve fewer keys than expected.",
                    methodId, trimmed);
            }
        }

        return await _context.Set<ApiKey>().Where(k => keyIds.Contains(k.Id)).ToListAsync(cancellationToken);
    }

    public async Task AddApiMethodAsync(ApiMethod method, CancellationToken cancellationToken = default)
        => await _context.Set<ApiMethod>().AddAsync(method, cancellationToken);

    public Task UpdateApiMethodAsync(ApiMethod method, CancellationToken cancellationToken = default)
    { _context.Set<ApiMethod>().Update(method); return Task.CompletedTask; }

    public async Task DeleteApiMethodAsync(int id, CancellationToken cancellationToken = default)
    {
        var entity = await GetApiMethodByIdAsync(id, cancellationToken);
        if (entity != null) _context.Set<ApiMethod>().Remove(entity);
    }

    public async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
        => await _context.SaveChangesAsync(cancellationToken);
}
