using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Assets;
using Tiku.Application.Jobs;
using Tiku.Application.Storage;
using Tiku.Domain.Content;
using Tiku.Infrastructure.Jobs;
using Tiku.Infrastructure.Persistence;

namespace Tiku.Infrastructure.Assets.Security;

internal sealed class AssetSecurityScanJobHandler(
    TikuDbContext dbContext,
    IAssetSecurityScanner scanner,
    IObjectStorageService storage) : IBackgroundJobHandler
{
    public string JobType => "asset_security_scan";

    public async Task<BackgroundJobHandlerResult> HandleAsync(
        BackgroundJobExecutionContext context,
        CancellationToken cancellationToken = default)
    {
        var assetId = BackgroundJobPayload.GetGuid(context.Payload, "assetId") ??
                      throw new InvalidOperationException("asset_security_scan job requires assetId.");
        var asset = await dbContext.ContentAssets.SingleOrDefaultAsync(
            item => item.TenantId == context.TenantId && item.Id == assetId,
            cancellationToken) ?? throw new InvalidOperationException("Asset security scan target was not found.");
        if (asset.UploadStatus != AssetUploadStatus.Verified ||
            string.IsNullOrWhiteSpace(asset.Bucket) ||
            string.IsNullOrWhiteSpace(asset.ObjectKey))
            throw new InvalidOperationException("Asset must have a verified object location before security scanning.");

        asset.SecurityScanStatus = AssetSecurityScanStatus.Scanning;
        await dbContext.SaveChangesAsync(cancellationToken);
        try
        {
            await using var content = await storage.OpenReadAsync(
                new ObjectStorageReadRequest(
                    context.TenantId,
                    asset.StorageProvider switch
                    {
                        AssetStorageProvider.AliyunOss => ObjectStorageProviders.AliyunOss,
                        AssetStorageProvider.LocalDev => ObjectStorageProviders.LocalDev,
                        _ => throw new InvalidOperationException(
                            "Asset storage provider does not support security scanning.")
                    },
                    asset.Bucket,
                    asset.ObjectKey),
                cancellationToken);
            var result =
                await scanner.ScanAsync(content, asset.VerifiedSizeBytes ?? asset.FileSizeBytes, cancellationToken);
            var infected = result.Verdict == AssetSecurityScanVerdict.Infected;
            asset.SecurityScanStatus = infected ? AssetSecurityScanStatus.Failed : AssetSecurityScanStatus.Passed;
            asset.SecurityScannedAt = DateTimeOffset.UtcNow;
            asset.SecurityScanProvider = result.Provider;
            asset.SecurityScanSummary = JsonSerializer.SerializeToElement(new
            {
                verdict = result.Verdict.ToString(),
                result.Signature,
                result.BytesScanned
            });
            dbContext.ContentAssetSecurityScanEvents.Add(new ContentAssetSecurityScanEvent
            {
                TenantId = context.TenantId,
                AssetId = asset.Id,
                Provider = result.Provider,
                ScanStatus = asset.SecurityScanStatus,
                RiskLevel = infected ? AssetSecurityRiskLevel.Critical : AssetSecurityRiskLevel.None,
                IssueCodes = infected ? [result.Signature ?? "malware_detected"] : [],
                Details = asset.SecurityScanSummary
            });
            await dbContext.SaveChangesAsync(cancellationToken);
            return new BackgroundJobHandlerResult(JsonSerializer.SerializeToElement(new
            {
                assetId = asset.Id,
                status = asset.SecurityScanStatus.ToString(),
                result.Signature,
                result.BytesScanned
            }));
        }
        catch (AssetSecurityScannerException exception)
        {
            throw new BackgroundJobHandlerException(exception.Code, exception.Message, exception);
        }
    }

    public async Task HandleFailureAsync(
        BackgroundJobExecutionContext context,
        Exception exception,
        CancellationToken cancellationToken = default)
    {
        if (exception is not BackgroundJobHandlerException handlerException ||
            handlerException.InnerException is not AssetSecurityScannerException)
            return;

        var assetId = BackgroundJobPayload.GetGuid(context.Payload, "assetId");
        if (assetId is null) return;
        var asset = await dbContext.ContentAssets.SingleOrDefaultAsync(
            item => item.TenantId == context.TenantId && item.Id == assetId,
            cancellationToken);
        if (asset is null) return;

        asset.SecurityScanStatus = AssetSecurityScanStatus.Pending;
        asset.SecurityScanProvider = "clamav";
        asset.SecurityScanSummary = JsonSerializer.SerializeToElement(new { errorCode = handlerException.Code });
        dbContext.ContentAssetSecurityScanEvents.Add(new ContentAssetSecurityScanEvent
        {
            TenantId = context.TenantId,
            AssetId = asset.Id,
            Provider = "clamav",
            ScanStatus = AssetSecurityScanStatus.Pending,
            RiskLevel = AssetSecurityRiskLevel.None,
            IssueCodes = [handlerException.Code],
            Details = asset.SecurityScanSummary
        });
        await dbContext.SaveChangesAsync(cancellationToken);
    }
}
