using Indotalent.Data.Entities;
using Indotalent.Infrastructure.Database;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using System.Collections.Concurrent;
using System.Data;

namespace Indotalent.Infrastructure.AutoNumberGenerator;

public class AutoNumberGeneratorService
{
    private readonly AppDbContext _context;
    private static readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new();

    public AutoNumberGeneratorService(AppDbContext context)
    {
        _context = context;
    }

    public async Task<string> GenerateNextNumberAsync(
        string tenantId,
        string entityName,
        string prefixTemplate,
        string? suffixTemplate = null,
        int paddingLength = 4,
        bool useYear = true,
        bool useMonth = false,
        CancellationToken cancellationToken = default)
    {
        var now = DateTime.UtcNow;
        int? year = useYear ? now.Year : null;
        int? month = useMonth ? now.Month : null;

        var lockKey = $"{tenantId}_{entityName}_{year}_{month}";
        var semaphore = _locks.GetOrAdd(lockKey, _ => new SemaphoreSlim(1, 1));

        await semaphore.WaitAsync(cancellationToken);
        try
        {
            var connection = _context.Database.GetDbConnection();
            if (connection.State != ConnectionState.Open)
            {
                await connection.OpenAsync(cancellationToken);
            }

            var currentEfTransaction = _context.Database.CurrentTransaction?.GetDbTransaction();

            long currentSequence = 0;

            var sql = @"
                MERGE INTO AutoNumberSequence AS Target
                USING (SELECT @TenantId AS TenantId, @EntityName AS EntityName, @Year AS Year, @Month AS Month) AS Source
                ON (Target.TenantId = Source.TenantId AND Target.EntityName = Source.EntityName 
                    AND (Target.Year = Source.Year OR (Target.Year IS NULL AND Source.Year IS NULL))
                    AND (Target.Month = Source.Month OR (Target.Month IS NULL AND Source.Month IS NULL)))
                WHEN MATCHED THEN
                    UPDATE SET Target.CurrentSequence = Target.CurrentSequence + 1
                WHEN NOT MATCHED THEN
                    INSERT (Id, TenantId, EntityName, Year, Month, CurrentSequence, PrefixTemplate, SuffixTemplate, PaddingLength, IsDeleted)
                    VALUES (@Id, Source.TenantId, Source.EntityName, Source.Year, Source.Month, 1, @PrefixTemplate, @SuffixTemplate, @PaddingLength, 0);

                SELECT CurrentSequence FROM AutoNumberSequence 
                WHERE TenantId = @TenantId AND EntityName = @EntityName 
                  AND (Year = @Year OR (Year IS NULL AND @Year IS NULL))
                  AND (Month = @Month OR (Month IS NULL AND @Month IS NULL));";

            using (var command = connection.CreateCommand())
            {
                command.CommandText = sql;
                command.CommandType = CommandType.Text;

                if (currentEfTransaction != null)
                {
                    command.Transaction = currentEfTransaction;
                }

                command.Parameters.Add(new SqlParameter("@Id", Guid.NewGuid().ToString("N").Substring(0, 30)));
                command.Parameters.Add(new SqlParameter("@TenantId", tenantId));
                command.Parameters.Add(new SqlParameter("@EntityName", entityName));
                command.Parameters.Add(new SqlParameter("@Year", (object?)year ?? DBNull.Value));
                command.Parameters.Add(new SqlParameter("@Month", (object?)month ?? DBNull.Value));
                command.Parameters.Add(new SqlParameter("@PrefixTemplate", prefixTemplate));
                command.Parameters.Add(new SqlParameter("@SuffixTemplate", (object?)suffixTemplate ?? DBNull.Value));
                command.Parameters.Add(new SqlParameter("@PaddingLength", paddingLength));

                var result = await command.ExecuteScalarAsync(cancellationToken);
                currentSequence = Convert.ToInt64(result);
            }

            var formattedNumber = BuildFormattedNumber(
                entityName: entityName,
                prefixTemplate: prefixTemplate,
                suffixTemplate: suffixTemplate,
                paddingLength: paddingLength,
                sequence: currentSequence,
                now: now
            );

            return formattedNumber;
        }
        finally
        {
            semaphore.Release();
            if (semaphore.CurrentCount == 1) _locks.TryRemove(lockKey, out _);
        }
    }

    private string BuildFormattedNumber(
        string entityName,
        string prefixTemplate,
        string? suffixTemplate,
        int paddingLength,
        long sequence,
        DateTime now)
    {
        string ProcessTemplate(string? template)
        {
            if (string.IsNullOrEmpty(template)) return "";
            return template
                .Replace("{EntityName}", entityName)
                .Replace("{Year}", now.Year.ToString())
                .Replace("{Year2}", now.ToString("yy"))
                .Replace("{Month}", now.Month.ToString("D2"));
        }

        var prefix = ProcessTemplate(prefixTemplate);
        var suffix = ProcessTemplate(suffixTemplate);
        var paddedSequence = sequence.ToString($"D{paddingLength}");

        return $"{prefix}{paddedSequence}{suffix}";
    }
}