using System.Buffers.Binary;
using System.Text.Json;
using Hangfire;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Products.Enums;
using TakeoutSaaS.Infrastructure.App.Persistence;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
using TakeoutSaaS.TenantApi.Controllers;

namespace TakeoutSaaS.TenantApi.Services;

/// <summary>
/// 商品 SKU 异步保存任务执行器。
/// </summary>
public sealed class ProductSkuSaveJobRunner(
    TakeoutAppDbContext dbContext,
    ITenantContextAccessor tenantContextAccessor,
    ProductSkuSaveService productSkuSaveService,
    ILogger<ProductSkuSaveJobRunner> logger)
{
    /// <summary>
    /// 执行指定任务。
    /// </summary>
    [AutomaticRetry(Attempts = 0)]
    public async Task ExecuteAsync(long jobId)
    {
        var jobMeta = await dbContext.ProductSkuSaveJobs
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(item => item.Id == jobId)
            .Select(item => new JobMeta(item.Id, item.TenantId))
            .SingleOrDefaultAsync();

        if (jobMeta is null || jobMeta.TenantId <= 0)
        {
            logger.LogWarning("SKU 异步保存任务不存在或租户无效，JobId={JobId}", jobId);
            return;
        }

        using var _ = tenantContextAccessor.EnterTenantScope(jobMeta.TenantId, "scheduler", $"tenant-{jobMeta.TenantId}");
        try
        {
            await RunWithExecutionStrategyAsync(jobMeta.Id);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "SKU 异步保存任务执行失败，JobId={JobId}", jobId);
            await MarkFailedAsync(jobMeta.Id, BuildDetailedErrorMessage(ex));
        }
    }

    private async Task RunWithExecutionStrategyAsync(long jobId)
    {
        var executionStrategy = dbContext.Database.CreateExecutionStrategy();
        await executionStrategy.ExecuteAsync(async () =>
        {
            dbContext.ChangeTracker.Clear();

            await using var transaction = await dbContext.Database.BeginTransactionAsync();
            var job = await dbContext.ProductSkuSaveJobs
                .SingleOrDefaultAsync(item => item.Id == jobId);
            if (job is null)
            {
                return;
            }

            if (job.Status == ProductSkuSaveJobStatus.Succeeded)
            {
                return;
            }

            job.Status = ProductSkuSaveJobStatus.Running;
            job.StartedAt ??= DateTime.UtcNow;
            job.FinishedAt = null;
            job.ErrorMessage = null;
            job.FailedCount = 0;
            job.ProgressProcessed = 0;
            await dbContext.SaveChangesAsync();

            // 以租户+商品维度申请事务级咨询锁，保证同商品串行落库。
            var advisoryLockKey = BuildAdvisoryLockKey(job.TenantId, job.ProductId);
            await dbContext.Database.ExecuteSqlInterpolatedAsync(
                $"SELECT pg_advisory_xact_lock({advisoryLockKey});");

            var payload = DeserializePayload(job.PayloadJson);
            if (payload.Skus.Count == 0)
            {
                throw new InvalidOperationException("SKU 保存任务缺少有效数据。");
            }

            var productExists = await dbContext.Products
                .AsNoTracking()
                .AnyAsync(item => item.Id == job.ProductId && item.StoreId == job.StoreId);
            if (!productExists)
            {
                throw new InvalidOperationException($"商品不存在或不属于门店，ProductId={job.ProductId}");
            }

            await productSkuSaveService.ReplaceSkusAsync(
                job.ProductId,
                job.StoreId,
                payload.Skus,
                payload.SpecTemplateIds,
                CancellationToken.None);

            job.Status = ProductSkuSaveJobStatus.Succeeded;
            job.ProgressProcessed = job.ProgressTotal;
            job.FailedCount = 0;
            job.FinishedAt = DateTime.UtcNow;
            job.ErrorMessage = null;
            await dbContext.SaveChangesAsync();
            await transaction.CommitAsync();
        });
    }

    private async Task MarkFailedAsync(long jobId, string errorMessage)
    {
        var executionStrategy = dbContext.Database.CreateExecutionStrategy();
        await executionStrategy.ExecuteAsync(async () =>
        {
            dbContext.ChangeTracker.Clear();

            var job = await dbContext.ProductSkuSaveJobs
                .SingleOrDefaultAsync(item => item.Id == jobId);
            if (job is null)
            {
                return;
            }

            job.Status = ProductSkuSaveJobStatus.Failed;
            job.FinishedAt = DateTime.UtcNow;
            job.ErrorMessage = Truncate(errorMessage, 2000);
            job.FailedCount = Math.Max(1, job.ProgressTotal - job.ProgressProcessed);
            await dbContext.SaveChangesAsync();
        });
    }

    private static ProductSkuSaveJobPayload DeserializePayload(string payloadJson)
    {
        if (string.IsNullOrWhiteSpace(payloadJson))
        {
            throw new InvalidOperationException("SKU 保存任务负载为空。");
        }

        try
        {
            var payload = JsonSerializer.Deserialize<ProductSkuSaveJobPayload>(payloadJson, StoreApiHelpers.JsonOptions);
            if (payload is null)
            {
                throw new InvalidOperationException("SKU 保存任务负载解析结果为空。");
            }

            payload.Skus ??= [];
            payload.SpecTemplateIds ??= [];
            return payload;
        }
        catch (JsonException ex)
        {
            throw new InvalidOperationException("SKU 保存任务负载解析失败。", ex);
        }
    }

    private static string Truncate(string? value, int maxLength)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }

        return value.Length <= maxLength ? value : value[..maxLength];
    }

    private static string BuildDetailedErrorMessage(Exception ex)
    {
        var lines = new List<string>();
        var current = ex;
        var depth = 0;
        const int maxDepth = 8;

        while (current is not null && depth < maxDepth)
        {
            var prefix = depth == 0 ? "Exception" : $"Inner[{depth}]";
            var message = string.IsNullOrWhiteSpace(current.Message)
                ? "(no message)"
                : current.Message.Trim();
            lines.Add($"{prefix} {current.GetType().Name}: {message}");
            current = current.InnerException;
            depth++;
        }

        if (current is not null)
        {
            lines.Add($"Inner[{depth}] ...");
        }

        return string.Join(Environment.NewLine, lines);
    }

    private static long BuildAdvisoryLockKey(long tenantId, long productId)
    {
        Span<byte> raw = stackalloc byte[16];
        BinaryPrimitives.WriteInt64LittleEndian(raw, tenantId);
        BinaryPrimitives.WriteInt64LittleEndian(raw[8..], productId);

        const ulong fnvOffsetBasis = 14695981039346656037UL;
        const ulong fnvPrime = 1099511628211UL;
        var hash = fnvOffsetBasis;
        foreach (var b in raw)
        {
            hash ^= b;
            hash *= fnvPrime;
        }

        return unchecked((long)hash);
    }

    private sealed record JobMeta(long Id, long TenantId);
}
