// EN: Middleware to set PostgreSQL session variable for RLS policies.
// VI: Middleware để đặt biến session PostgreSQL cho RLS policies.

using System.Data;
using Npgsql;
using FnbEngine.API.Infrastructure.Tenant;

namespace FnbEngine.API.Middleware;

/// <summary>
/// EN: Sets PostgreSQL session variables for row-level security defense-in-depth.
/// VI: Đặt biến session PostgreSQL cho bảo mật row-level phòng thủ theo chiều sâu.
/// </summary>
public class TenantMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<TenantMiddleware> _logger;

    public TenantMiddleware(RequestDelegate next, ILogger<TenantMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context, ITenantProvider tenantProvider, IDbConnection dbConnection)
    {
        var shopId = tenantProvider.GetCurrentShopId();
        var merchantId = tenantProvider.GetCurrentMerchantId();
        var isServiceCall = tenantProvider.IsServiceCall();
        var isAdmin = tenantProvider.IsAdmin();

        // EN: Skip tenant SET for service-to-service calls and admin users
        // VI: Bỏ qua tenant SET cho cuộc gọi service-to-service và admin users
        if (!isServiceCall && !isAdmin)
        {
            if (shopId.HasValue)
            {
                await SetTenantContextAsync(dbConnection, shopId.Value, merchantId);
            }
            else
            {
                _logger.LogDebug(
                    "EN: No tenant context available for request {Path} / " +
                    "VI: Không có tenant context cho request {Path}",
                    context.Request.Path);
            }
        }

        await _next(context);
    }

    /// <summary>
    /// EN: Set PostgreSQL session variables for RLS enforcement.
    /// VI: Đặt biến session PostgreSQL cho RLS enforcement.
    /// </summary>
    private async Task SetTenantContextAsync(IDbConnection dbConnection, Guid shopId, Guid? merchantId)
    {
        try
        {
            if (dbConnection is NpgsqlConnection npgsqlConnection)
            {
                if (npgsqlConnection.State != ConnectionState.Open)
                {
                    await npgsqlConnection.OpenAsync();
                }

                // EN: Set shop_id as the primary tenant identifier using parameterized set_config()
                //     to prevent SQL injection. set_config(key, value, is_local) is equivalent
                //     to SET LOCAL but supports proper parameter binding.
                // VI: Đặt shop_id làm tenant identifier chính dùng set_config() có tham số
                //     để ngăn SQL injection. set_config(key, value, is_local) tương đương
                //     SET LOCAL nhưng hỗ trợ parameter binding đúng cách.
                await using var cmd = npgsqlConnection.CreateCommand();
                cmd.CommandText = "SELECT set_config('app.current_shop_id', $1, true)";
                cmd.Parameters.AddWithValue(shopId.ToString("D"));
                await cmd.ExecuteNonQueryAsync();

                if (merchantId.HasValue)
                {
                    cmd.Parameters.Clear();
                    cmd.CommandText = "SELECT set_config('app.current_merchant_id', $1, true)";
                    cmd.Parameters.AddWithValue(merchantId.Value.ToString("D"));
                    await cmd.ExecuteNonQueryAsync();
                }

                _logger.LogDebug(
                    "EN: Tenant context set - ShopId: {ShopId}, MerchantId: {MerchantId} / " +
                    "VI: Tenant context đã đặt - ShopId: {ShopId}, MerchantId: {MerchantId}",
                    shopId, merchantId);
            }
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex,
                "EN: Failed to set PostgreSQL tenant context / " +
                "VI: Không thể đặt PostgreSQL tenant context");
        }
    }
}

/// <summary>
/// EN: Extension method for registering TenantMiddleware.
/// VI: Extension method để đăng ký TenantMiddleware.
/// </summary>
public static class TenantMiddlewareExtensions
{
    public static IApplicationBuilder UseTenantMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<TenantMiddleware>();
    }
}
