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

namespace Tiku.Infrastructure.Commerce;

internal sealed partial class ReconciliationAdministrationService
{
    public async Task<TenantReconciliationItemList> GetReconciliationItemsAsync(
        CommerceAdminActor actor,
        Guid batchId,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var batchExists = await commercePersistence.CommerceReconciliationBatches.AnyAsync(
            item => item.TenantId == actor.TenantId && item.Id == batchId,
            cancellationToken);
        if (!batchExists)
            throw new CommerceException("Reconciliation batch was not found.", "reconciliation_batch_not_found");

        var items = await commercePersistence.CommerceReconciliationItems.AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId && item.BatchId == batchId)
            .OrderBy(item => item.RowNo)
            .Take(500)
            .ToArrayAsync(cancellationToken);
        return new TenantReconciliationItemList(items);
    }

    public async Task<TenantReconciliationIssueEventList> GetReconciliationIssueEventsAsync(
        CommerceAdminActor actor,
        Guid issueId,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var issueExists = await commercePersistence.CommerceReconciliationIssues.AnyAsync(
            item => item.TenantId == actor.TenantId && item.Id == issueId,
            cancellationToken);
        if (!issueExists)
            throw new CommerceException("Reconciliation issue was not found.", "reconciliation_issue_not_found");

        var events = await commercePersistence.CommerceReconciliationIssueEvents.AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId && item.IssueId == issueId)
            .OrderBy(item => item.CreatedAt)
            .ToArrayAsync(cancellationToken);
        return new TenantReconciliationIssueEventList(events);
    }

    public async Task<ReconciliationImportPreview> PreviewReconciliationImportAsync(
            CommerceAdminActor actor,
            PreviewReconciliationImportCommand command,
            CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        return BuildImportPreview(command.Provider, command.Rows);
    }

    public async Task<CommerceReconciliationBatch> ImportReconciliationAsync(
        CommerceAdminActor actor,
        ImportReconciliationCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var preview = BuildImportPreview(command.Provider, command.Rows);
        var batch = new CommerceReconciliationBatch
        {
            TenantId = actor.TenantId,
            CreatedBy = actor.UserId,
            Provider = NormalizeProvider(command.Provider),
            BillDate = command.BillDate,
            BillType = command.BillType,
            Source = ReconciliationSource.ManualUpload,
            SourceName = command.SourceName.Trim(),
            SourceHash = preview.SourceHash,
            Status = preview.InvalidCount == 0
                ? ReconciliationBatchStatus.Completed
                : ReconciliationBatchStatus.CompletedWithIssues,
            TotalCount = preview.TotalCount,
            MatchedCount = preview.TotalCount - preview.InvalidCount,
            MismatchCount = preview.InvalidCount,
            AmountCents = preview.AmountCents,
            RefundAmountCents = preview.RefundAmountCents,
            CompletedAt = DateTimeOffset.UtcNow,
            Metadata = JsonSerializer.SerializeToElement(new
            {
                preview.PaymentCount,
                preview.RefundCount,
                preview.InvalidCount
            })
        };
        commercePersistence.CommerceReconciliationBatches.Add(batch);
        var rowNo = 0;
        foreach (var row in EnumerateImportRows(command.Rows))
        {
            rowNo++;
            var item = CreateReconciliationItem(actor.TenantId, batch.Id, rowNo, NormalizeProvider(command.Provider),
                row);
            commercePersistence.CommerceReconciliationItems.Add(item);
            if (item.MatchStatus != ReconciliationMatchStatus.Matched)
                commercePersistence.CommerceReconciliationIssues.Add(new CommerceReconciliationIssue
                {
                    TenantId = actor.TenantId,
                    BatchId = batch.Id,
                    Provider = item.Provider,
                    TransactionType = item.TransactionType,
                    IssueNo = $"RC{DateTimeOffset.UtcNow:yyyyMMddHHmmss}{rowNo:0000}",
                    MatchStatus = item.MatchStatus switch
                    {
                        ReconciliationMatchStatus.MissingLocal => ReconciliationIssueMatchStatus.MissingLocal,
                        ReconciliationMatchStatus.MissingProvider => ReconciliationIssueMatchStatus.MissingProvider,
                        ReconciliationMatchStatus.Duplicate => ReconciliationIssueMatchStatus.Duplicate,
                        ReconciliationMatchStatus.StatusMismatch => ReconciliationIssueMatchStatus.StatusMismatch,
                        _ => ReconciliationIssueMatchStatus.AmountMismatch
                    },
                    Severity = item.Severity,
                    Status = ReconciliationIssueStatus.Open,
                    OrderNo = item.OrderNo,
                    RefundNo = item.RefundNo,
                    ProviderTradeNo = item.ProviderTradeNo,
                    ProviderRefundNo = item.ProviderRefundNo,
                    AmountCents = item.AmountCents,
                    RefundAmountCents = item.RefundAmountCents,
                    Summary = item.IssueCode,
                    CreatedBy = actor.UserId,
                    Metadata = item.Details
                });
        }

        await AddAuditAsync(actor, "commerce.reconciliation.imported", "commerce_reconciliation_batches", batch.Id,
            new { batch.Provider, batch.BillDate, batch.TotalCount }, cancellationToken);
        await unitOfWork.SaveChangesAsync(cancellationToken);
        return batch;
    }

    public async Task<BackgroundJobItem> RequestProviderBillJobAsync(
        CommerceAdminActor actor,
        RequestProviderBillJobCommand command,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        var job = await backgroundJobQueue.EnqueueAsync(
            new CreateBackgroundJobCommand(
                actor.TenantId,
                "commerce_reconciliation",
                JsonSerializer.SerializeToElement(new
                {
                    provider = NormalizeProvider(command.Provider),
                    command.BillDate,
                    billType = command.BillType.ToString()
                }),
                command.RunAfter,
                5),
            cancellationToken);
        await AddAuditAsync(actor, "commerce.reconciliation.provider_bill_requested", "background_jobs", job.Id,
            new { command.Provider, command.BillDate, command.BillType }, cancellationToken);
        await unitOfWork.SaveChangesAsync(cancellationToken);
        return job;
    }

    public async Task<IReadOnlyCollection<BackgroundJobItem>> GetProviderBillJobsAsync(
        CommerceAdminActor actor,
        CommerceAdminQuery query,
        CancellationToken cancellationToken = default)
    {
        await AssertAdminAsync(actor, cancellationToken);
        return await backgroundJobOperations.ListAsync(actor.TenantId, "commerce_reconciliation",
            Math.Clamp(query.Limit ?? 50, 1, 200), cancellationToken);
    }
}
