using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Tiku.Application.Jobs;
using Tiku.Domain.Operations;

namespace Tiku.Infrastructure.Jobs;

internal sealed partial class BackgroundJobService
{
    public async Task<IReadOnlyCollection<BackgroundJobItem>> ListAsync(
        Guid tenantId,
        string? jobType = null,
        int limit = 50,
        CancellationToken cancellationToken = default)
    {
        var query = dbContext.BackgroundJobs.AsNoTracking()
            .Where(job => job.TenantId == tenantId);
        if (!string.IsNullOrWhiteSpace(jobType))
        {
            var normalized = NormalizeJobType(jobType);
            query = query.Where(job => job.JobType == normalized);
        }

        var jobs = await query
            .OrderByDescending(job => job.CreatedAt)
            .Take(Math.Clamp(limit, 1, 200))
            .ToArrayAsync(cancellationToken);
        return jobs.Select(ToItem).ToArray();
    }

    public async Task<BackgroundJobItem?> GetAsync(
        Guid jobId,
        Guid? tenantId,
        CancellationToken cancellationToken = default)
    {
        var query = dbContext.BackgroundJobs.AsNoTracking().Where(item => item.Id == jobId);
        if (tenantId.HasValue) query = query.Where(item => item.TenantId == tenantId.Value);
        var job = await query.SingleOrDefaultAsync(cancellationToken);
        return job is null ? null : ToItem(job);
    }

    public async Task<IReadOnlyCollection<BackgroundJobItem>> ListPlatformAsync(
        Guid? tenantId = null,
        string? jobType = null,
        BackgroundJobStatus? status = null,
        int limit = 100,
        CancellationToken cancellationToken = default)
    {
        var query = dbContext.BackgroundJobs.AsNoTracking().AsQueryable();
        if (tenantId.HasValue) query = query.Where(item => item.TenantId == tenantId.Value);
        if (!string.IsNullOrWhiteSpace(jobType))
        {
            var normalized = NormalizeJobType(jobType);
            query = query.Where(item => item.JobType == normalized);
        }

        if (status.HasValue) query = query.Where(item => item.Status == status.Value);
        return (await query.OrderByDescending(item => item.CreatedAt)
                .Take(Math.Clamp(limit, 1, 500))
                .ToArrayAsync(cancellationToken))
            .Select(ToItem)
            .ToArray();
    }

    public async Task<BackgroundJobItem> RequestCancellationAsync(
        Guid jobId,
        Guid? tenantId,
        Guid actorUserId,
        string reason,
        CancellationToken cancellationToken = default)
    {
        dbContext.ChangeTracker.Clear();
        if (string.IsNullOrWhiteSpace(reason))
            throw new BackgroundJobException("background_job_cancel_reason_required",
                "Cancellation reason is required.");
        var job = await FindMutableAsync(jobId, tenantId, cancellationToken);
        if (job.Status is BackgroundJobStatus.Succeeded or BackgroundJobStatus.Failed or BackgroundJobStatus.Cancelled)
            throw new BackgroundJobException("background_job_not_cancellable",
                "Only pending or processing jobs can be cancelled.");

        var now = DateTimeOffset.UtcNow;
        job.CancellationRequestedAt = now;
        job.CancellationRequestedBy = actorUserId;
        job.CancellationReason = reason.Trim();
        if (job.Status == BackgroundJobStatus.Pending)
        {
            job.Status = BackgroundJobStatus.Cancelled;
            job.CompletedAt = now;
        }

        AddMutationAudit(job, actorUserId, "background_job.cancel_requested");
        await dbContext.SaveChangesAsync(cancellationToken);
        return ToItem(job);
    }

    public async Task<BackgroundJobItem> RetryAsync(
        Guid jobId,
        Guid? tenantId,
        Guid actorUserId,
        CancellationToken cancellationToken = default)
    {
        dbContext.ChangeTracker.Clear();
        var job = await FindMutableAsync(jobId, tenantId, cancellationToken);
        if (job.Status is not (BackgroundJobStatus.Failed or BackgroundJobStatus.Cancelled))
            throw new BackgroundJobException("background_job_not_retryable",
                "Only failed or cancelled jobs can be retried.");

        job.Status = BackgroundJobStatus.Pending;
        job.RunAfter = DateTimeOffset.UtcNow;
        job.StartedAt = null;
        job.CompletedAt = null;
        job.LockedBy = null;
        job.LockExpiresAt = null;
        job.LastError = null;
        job.CancellationRequestedAt = null;
        job.CancellationRequestedBy = null;
        job.CancellationReason = null;
        AddMutationAudit(job, actorUserId, "background_job.retry_requested");
        await dbContext.SaveChangesAsync(cancellationToken);
        return ToItem(job);
    }

    private async Task<BackgroundJob> FindMutableAsync(
        Guid jobId,
        Guid? tenantId,
        CancellationToken cancellationToken)
    {
        var query = dbContext.BackgroundJobs.Where(item => item.Id == jobId);
        if (tenantId.HasValue) query = query.Where(item => item.TenantId == tenantId.Value);
        return await query.SingleOrDefaultAsync(cancellationToken) ??
               throw new BackgroundJobException("background_job_not_found", "Background job was not found.");
    }

    private void AddMutationAudit(BackgroundJob job, Guid actorUserId, string action)
    {
        dbContext.AuditLogs.Add(new AuditLog
        {
            TenantId = job.TenantId,
            ActorUserId = actorUserId,
            Action = action,
            TargetType = "background_job",
            TargetId = job.Id.ToString(),
            Details = JsonSerializer.SerializeToElement(new { job.JobType, job.Status })
        });
    }
}