using System.Data;
using ERPCore.Infra.Persistence;
using ERPCore.Services.Interfaces;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;

namespace ERPCore.Services;

/// <summary>
/// Atomic document-number issuer. Uses a single <c>INSERT … ON CONFLICT … DO UPDATE
/// … RETURNING</c> so concurrent issues for the same (docType, year) cannot get the
/// same number (the row is locked for the duration of the upsert). Runs as a direct
/// ADO.NET command enlisted in the DbContext's current transaction so it commits or
/// rolls back with the document (FR-X-03). EF's <c>SqlQuery</c> is avoided here
/// because it wraps the statement in a subquery, which PostgreSQL disallows for a
/// data-modifying statement.
/// </summary>
public sealed class NumberSequenceService : INumberSequenceService
{
    private readonly ErpDbContext _db;

    public NumberSequenceService(ErpDbContext db) => _db = db;

    public async Task<string> NextAsync(string docType, CancellationToken ct = default)
    {
        var year = DateTime.UtcNow.Year;

        var conn = _db.Database.GetDbConnection();
        if (conn.State != ConnectionState.Open)
            await conn.OpenAsync(ct);

        await using var cmd = conn.CreateCommand();
        cmd.Transaction = _db.Database.CurrentTransaction?.GetDbTransaction();
        cmd.CommandText = """
            INSERT INTO number_sequences (doc_type, year, last_number)
            VALUES (@docType, @year, 1)
            ON CONFLICT (doc_type, year)
            DO UPDATE SET last_number = number_sequences.last_number + 1
            RETURNING last_number;
            """;
        AddParam(cmd, "docType", docType);
        AddParam(cmd, "year", year);

        var result = await cmd.ExecuteScalarAsync(ct)
            ?? throw new InvalidOperationException($"Failed to issue a document number for '{docType}'.");
        var next = Convert.ToInt64(result);

        return $"{docType}-{year}-{next:D5}";
    }

    private static void AddParam(IDbCommand cmd, string name, object value)
    {
        var p = cmd.CreateParameter();
        p.ParameterName = name;
        p.Value = value;
        cmd.Parameters.Add(p);
    }
}
