using CashLineERP.Application.Tax;
using CashLineERP.Domain.Tax;
using CashLineERP.SharedKernel.Tax;
using CashLineERP.SharedKernel.Workflow;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class EtaComplianceRepository : IEtaComplianceRepository
{
    private readonly CashLineErpDbContext _dbContext;

    public EtaComplianceRepository(CashLineErpDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public EtaPayloadPreviewView? GetPayloadPreview(long accountsReceivableInvoiceId)
    {
        var invoice = _dbContext.AccountsReceivableInvoices
            .AsNoTracking()
            .Include(existingInvoice => existingInvoice.Lines)
            .SingleOrDefault(existingInvoice => existingInvoice.Id == accountsReceivableInvoiceId);

        return invoice is null ? null : BuildPayloadPreview(invoice);
    }

    public EtaSubmissionValidationPackView? GetSubmissionValidationPack(long accountsReceivableInvoiceId)
    {
        var invoice = _dbContext.AccountsReceivableInvoices
            .AsNoTracking()
            .Include(existingInvoice => existingInvoice.Lines)
            .SingleOrDefault(existingInvoice => existingInvoice.Id == accountsReceivableInvoiceId);

        if (invoice is null)
        {
            return null;
        }

        var customer = _dbContext.BusinessParties
            .AsNoTracking()
            .SingleOrDefault(existingParty => existingParty.Id == invoice.CustomerId);
        var branch = ResolveBranch(invoice);
        var buildResult = EtaPayloadBuilder.Build(invoice, customer, branch);

        return new EtaSubmissionValidationPackView(
            invoice.Id,
            invoice.InvoiceNumber,
            buildResult.NormalizedDocumentType,
            branch?.BranchCode,
            branch?.TaxBranchCode,
            customer?.PartyCode,
            buildResult.IsStatutoryReady,
            buildResult.ValidationRules);
    }

    public IReadOnlyCollection<EtaSubmissionLogView> GetSubmissionLogs(long accountsReceivableInvoiceId) =>
        _dbContext.EtaSubmissionLogs
            .AsNoTracking()
            .Where(log => log.AccountsReceivableInvoiceId == accountsReceivableInvoiceId)
            .OrderByDescending(log => log.Id)
            .Select(Map)
            .ToArray();

    public IReadOnlyCollection<EtaDocumentEventView> GetDocumentEvents(long accountsReceivableInvoiceId) =>
        _dbContext.EtaDocumentEvents
            .AsNoTracking()
            .Where(evt => evt.AccountsReceivableInvoiceId == accountsReceivableInvoiceId)
            .OrderByDescending(evt => evt.EventTimestampUtc)
            .Select(Map)
            .ToArray();

    public EtaSubmissionLogView SubmitInvoice(long accountsReceivableInvoiceId, long actorUserId, string? payloadText)
    {
        var invoice = _dbContext.AccountsReceivableInvoices
            .Include(existingInvoice => existingInvoice.Lines)
            .Single(existingInvoice => existingInvoice.Id == accountsReceivableInvoiceId);

        var preview = BuildPayloadPreview(invoice);

        if (invoice.StatusId != WorkflowStatusSeedIds.Posted)
        {
            throw new InvalidOperationException("Invoice must be posted before ETA submission.");
        }

        AccountingPeriodLockGuard.EnsureOpenPeriod(_dbContext, invoice.TaxDate ?? invoice.InvoiceDate, "ETA submission");

        if (!preview.IsStatutoryReady)
        {
            throw new InvalidOperationException(string.Join(" ", preview.ValidationMessages));
        }

        var nextSequence = (_dbContext.EtaSubmissionLogs.Max(log => (long?)log.Id) ?? 0L) + 1L;
        var submission = new EtaSubmissionLog(
            accountsReceivableInvoiceId: accountsReceivableInvoiceId,
            submissionReference: $"ETA-SUB-{nextSequence:D6}",
            statusCode: "SUBMITTED",
            submittedAtUtc: DateTime.UtcNow,
            payloadText: string.IsNullOrWhiteSpace(payloadText) ? preview.PayloadText : payloadText);

        _dbContext.EtaSubmissionLogs.Add(submission);
        _dbContext.SaveChanges();

        var etaEvent = new EtaDocumentEvent(
            accountsReceivableInvoiceId: accountsReceivableInvoiceId,
            etaSubmissionLogId: submission.Id,
            eventType: "SUBMISSION",
            eventStatus: "SUBMITTED",
            eventTimestampUtc: DateTime.UtcNow,
            actorUserId: actorUserId,
            payloadText: submission.PayloadText,
            responseText: null,
            notesText: $"Invoice submitted to ETA queue with statutory-ready payload. Validation count: {preview.ValidationMessages.Count}.");

        _dbContext.EtaDocumentEvents.Add(etaEvent);
        invoice.MarkEta(null, "SUBMITTED", actorUserId);
        OutboxEventWriter.Enqueue(
            _dbContext,
            sourceModuleCode: "ETA",
            aggregateType: "AR_INVOICE",
            aggregateId: invoice.Id,
            eventType: "ETA_SUBMISSION_QUEUED",
            payload: new
            {
                invoice.InvoiceNumber,
                submission.SubmissionReference,
                preview.NormalizedDocumentType,
                preview.IsStatutoryReady
            },
            actorUserId: actorUserId,
            correlationKey: submission.SubmissionReference);
        _dbContext.SaveChanges();

        return Map(submission);
    }

    public EtaSubmissionLogView ResubmitRejectedInvoice(
        long accountsReceivableInvoiceId,
        long rejectedEtaSubmissionLogId,
        long actorUserId,
        string? payloadText,
        string? correctionNotes)
    {
        var invoice = _dbContext.AccountsReceivableInvoices
            .Include(existingInvoice => existingInvoice.Lines)
            .Single(existingInvoice => existingInvoice.Id == accountsReceivableInvoiceId);

        var rejectedSubmission = _dbContext.EtaSubmissionLogs.Single(existingLog =>
            existingLog.Id == rejectedEtaSubmissionLogId &&
            existingLog.AccountsReceivableInvoiceId == accountsReceivableInvoiceId);

        if (!string.Equals(rejectedSubmission.StatusCode, "REJECTED", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only rejected ETA submissions can be corrected and resubmitted.");
        }

        AccountingPeriodLockGuard.EnsureOpenPeriod(_dbContext, invoice.TaxDate ?? invoice.InvoiceDate, "ETA resubmission");
        var preview = BuildPayloadPreview(invoice);
        if (!preview.IsStatutoryReady)
        {
            throw new InvalidOperationException(string.Join(" ", preview.ValidationMessages));
        }

        var correctionEvent = new EtaDocumentEvent(
            accountsReceivableInvoiceId: accountsReceivableInvoiceId,
            etaSubmissionLogId: rejectedSubmission.Id,
            eventType: "CORRECTION",
            eventStatus: "PREPARED",
            eventTimestampUtc: DateTime.UtcNow,
            actorUserId: actorUserId,
            payloadText: string.IsNullOrWhiteSpace(payloadText) ? preview.PayloadText : payloadText,
            responseText: rejectedSubmission.ResponseText,
            notesText: string.IsNullOrWhiteSpace(correctionNotes)
                ? $"Correction prepared for rejected ETA submission {rejectedSubmission.SubmissionReference}."
                : correctionNotes);

        _dbContext.EtaDocumentEvents.Add(correctionEvent);
        _dbContext.SaveChanges();

        var nextSequence = (_dbContext.EtaSubmissionLogs.Max(log => (long?)log.Id) ?? 0L) + 1L;
        var resubmission = new EtaSubmissionLog(
            accountsReceivableInvoiceId: accountsReceivableInvoiceId,
            submissionReference: $"ETA-RESUB-{nextSequence:D6}",
            statusCode: "SUBMITTED",
            submittedAtUtc: DateTime.UtcNow,
            payloadText: string.IsNullOrWhiteSpace(payloadText) ? preview.PayloadText : payloadText);

        _dbContext.EtaSubmissionLogs.Add(resubmission);
        _dbContext.SaveChanges();

        var resubmissionEvent = new EtaDocumentEvent(
            accountsReceivableInvoiceId: accountsReceivableInvoiceId,
            etaSubmissionLogId: resubmission.Id,
            eventType: "RESUBMISSION",
            eventStatus: "SUBMITTED",
            eventTimestampUtc: DateTime.UtcNow,
            actorUserId: actorUserId,
            payloadText: resubmission.PayloadText,
            responseText: null,
            notesText: $"Rejected ETA submission {rejectedSubmission.SubmissionReference} corrected and resubmitted as {resubmission.SubmissionReference}.");

        _dbContext.EtaDocumentEvents.Add(resubmissionEvent);
        invoice.MarkEta(null, "RESUBMITTED", actorUserId);
        OutboxEventWriter.Enqueue(
            _dbContext,
            sourceModuleCode: "ETA",
            aggregateType: "AR_INVOICE",
            aggregateId: invoice.Id,
            eventType: "ETA_RESUBMISSION_QUEUED",
            payload: new
            {
                InvoiceNumber = invoice.InvoiceNumber,
                RejectedSubmissionReference = rejectedSubmission.SubmissionReference,
                ResubmissionReference = resubmission.SubmissionReference
            },
            actorUserId: actorUserId,
            correlationKey: resubmission.SubmissionReference);
        _dbContext.SaveChanges();

        return Map(resubmission);
    }

    public EtaSubmissionLogView RecordInvoiceResponse(
        long accountsReceivableInvoiceId,
        long etaSubmissionLogId,
        long actorUserId,
        string statusCode,
        string? etaUuid,
        string? responseText,
        string? errorCode,
        string? errorMessage,
        string? notesText)
    {
        var invoice = _dbContext.AccountsReceivableInvoices.Single(existingInvoice => existingInvoice.Id == accountsReceivableInvoiceId);
        var submission = _dbContext.EtaSubmissionLogs.Single(existingLog =>
            existingLog.Id == etaSubmissionLogId &&
            existingLog.AccountsReceivableInvoiceId == accountsReceivableInvoiceId);

        submission.RecordResponse(
            statusCode: statusCode,
            etaUuid: etaUuid,
            responseText: responseText,
            errorCode: errorCode,
            errorMessage: errorMessage,
            actorUserId: actorUserId);

        var etaEvent = new EtaDocumentEvent(
            accountsReceivableInvoiceId: accountsReceivableInvoiceId,
            etaSubmissionLogId: submission.Id,
            eventType: "RESPONSE",
            eventStatus: statusCode,
            eventTimestampUtc: DateTime.UtcNow,
            actorUserId: actorUserId,
            payloadText: null,
            responseText: responseText,
            notesText: notesText);

        _dbContext.EtaDocumentEvents.Add(etaEvent);
        invoice.MarkEta(etaUuid, statusCode, actorUserId);
        OutboxEventWriter.Enqueue(
            _dbContext,
            sourceModuleCode: "ETA",
            aggregateType: "AR_INVOICE",
            aggregateId: invoice.Id,
            eventType: "ETA_RESPONSE_RECORDED",
            payload: new
            {
                invoice.InvoiceNumber,
                submission.SubmissionReference,
                submission.StatusCode,
                submission.EtaUuid,
                submission.ErrorCode
            },
            actorUserId: actorUserId,
            correlationKey: submission.SubmissionReference);
        _dbContext.SaveChanges();

        return Map(submission);
    }

    public IReadOnlyCollection<EtaTransmissionAttemptView> GetTransmissions(long accountsReceivableInvoiceId)
    {
        var endpointCodeLookup = _dbContext.IntegrationEndpointRegistryEntries
            .AsNoTracking()
            .ToDictionary(entry => entry.Id, entry => entry.EndpointCode);

        return _dbContext.EtaTransmissionAttempts
            .AsNoTracking()
            .Where(entry => entry.AccountsReceivableInvoiceId == accountsReceivableInvoiceId)
            .OrderByDescending(entry => entry.Id)
            .ToArray()
            .Select(entry => Map(
                entry,
                endpointCodeLookup.TryGetValue(entry.IntegrationEndpointId, out var endpointCode)
                    ? endpointCode
                    : $"INTEP-MISSING-{entry.IntegrationEndpointId:D6}"))
            .ToArray();
    }

    public EtaTransmissionAttemptView? GetTransmission(long etaTransmissionAttemptId)
    {
        var transmission = _dbContext.EtaTransmissionAttempts
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == etaTransmissionAttemptId);

        if (transmission is null)
        {
            return null;
        }

        var endpointCode = _dbContext.IntegrationEndpointRegistryEntries
            .AsNoTracking()
            .Where(entry => entry.Id == transmission.IntegrationEndpointId)
            .Select(entry => entry.EndpointCode)
            .SingleOrDefault() ?? $"INTEP-MISSING-{transmission.IntegrationEndpointId:D6}";

        return Map(transmission, endpointCode);
    }

    public IReadOnlyCollection<EtaTransmissionAttemptView> GetPendingAuthorityStatusPollBatch(int batchSize)
    {
        if (batchSize is < 1 or > 100)
        {
            throw new InvalidOperationException("ETA authority status poll batch size must be between 1 and 100.");
        }

        var endpointCodeLookup = _dbContext.IntegrationEndpointRegistryEntries
            .AsNoTracking()
            .ToDictionary(entry => entry.Id, entry => entry.EndpointCode);

        return _dbContext.EtaTransmissionAttempts
            .AsNoTracking()
            .Where(entry => entry.StatusCode == "DISPATCHED")
            .OrderBy(entry => entry.DispatchedAtUtc)
            .ThenBy(entry => entry.Id)
            .Take(batchSize)
            .ToArray()
            .Select(entry => Map(
                entry,
                endpointCodeLookup.TryGetValue(entry.IntegrationEndpointId, out var endpointCode)
                    ? endpointCode
                    : $"INTEP-MISSING-{entry.IntegrationEndpointId:D6}"))
            .ToArray();
    }

    public EtaTransmissionAttemptView? FindPendingTransmissionByCorrelation(string correlationKey)
    {
        if (string.IsNullOrWhiteSpace(correlationKey))
        {
            return null;
        }

        var normalizedCorrelationKey = correlationKey.Trim();
        var endpointCodeLookup = _dbContext.IntegrationEndpointRegistryEntries
            .AsNoTracking()
            .ToDictionary(entry => entry.Id, entry => entry.EndpointCode);

        var candidate = (
            from transmission in _dbContext.EtaTransmissionAttempts.AsNoTracking()
            join submission in _dbContext.EtaSubmissionLogs.AsNoTracking()
                on transmission.EtaSubmissionLogId equals submission.Id
            where transmission.StatusCode == "DISPATCHED"
                  && (
                      submission.SubmissionReference == normalizedCorrelationKey
                      || submission.EtaUuid == normalizedCorrelationKey
                      || transmission.TransmissionNumber == normalizedCorrelationKey
                      || transmission.AdapterReference == normalizedCorrelationKey
                      || (submission.ResponseText != null && submission.ResponseText.Contains(normalizedCorrelationKey))
                  )
            orderby transmission.DispatchedAtUtc descending, transmission.Id descending
            select transmission)
            .FirstOrDefault();

        if (candidate is null)
        {
            return null;
        }

        return Map(
            candidate,
            endpointCodeLookup.TryGetValue(candidate.IntegrationEndpointId, out var endpointCode)
                ? endpointCode
                : $"INTEP-MISSING-{candidate.IntegrationEndpointId:D6}");
    }

    public EtaTransmissionAttemptView QueueTransmission(long accountsReceivableInvoiceId, long etaSubmissionLogId, long actorUserId)
    {
        var submission = _dbContext.EtaSubmissionLogs.Single(existingLog =>
            existingLog.Id == etaSubmissionLogId &&
            existingLog.AccountsReceivableInvoiceId == accountsReceivableInvoiceId);

        if (!string.Equals(submission.StatusCode, "SUBMITTED", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only submitted ETA logs can be queued for transmission.");
        }

        var existingActiveTransmission = _dbContext.EtaTransmissionAttempts
            .AsNoTracking()
            .Any(entry =>
                entry.EtaSubmissionLogId == etaSubmissionLogId &&
                entry.StatusCode != "FAILED");

        if (existingActiveTransmission)
        {
            throw new InvalidOperationException("ETA submission already has an active transport attempt.");
        }

        var endpoint = ResolveActiveEtaEndpoint();
        var nextSequence = (_dbContext.EtaTransmissionAttempts.Max(entry => (long?)entry.Id) ?? 0L) + 1L;
        var transmission = new EtaTransmissionAttempt(
            accountsReceivableInvoiceId,
            etaSubmissionLogId,
            endpoint.Id,
            $"ETA-TX-{nextSequence:D6}",
            submission.PayloadText ?? throw new InvalidOperationException("ETA submission payload is required before transport queueing."),
            DateTime.UtcNow);
        transmission.Touch(actorUserId);

        _dbContext.EtaTransmissionAttempts.Add(transmission);
        var etaEvent = new EtaDocumentEvent(
            accountsReceivableInvoiceId: accountsReceivableInvoiceId,
            etaSubmissionLogId: etaSubmissionLogId,
            eventType: "TRANSPORT",
            eventStatus: "QUEUED",
            eventTimestampUtc: DateTime.UtcNow,
            actorUserId: actorUserId,
            payloadText: transmission.PayloadText,
            responseText: null,
            notesText: $"ETA transmission {transmission.TransmissionNumber} queued for outbound adapter execution.");

        _dbContext.EtaDocumentEvents.Add(etaEvent);
        _dbContext.SaveChanges();
        return GetTransmissions(accountsReceivableInvoiceId).Single(entry => entry.EtaTransmissionAttemptId == transmission.Id);
    }

    public DispatchEtaTransmissionBatchResultView DispatchQueuedTransmissions(DispatchEtaTransmissionBatchCommand command)
    {
        var processorCode = command.ProcessorCode?.Trim().ToUpperInvariant();
        if (string.IsNullOrWhiteSpace(processorCode))
        {
            throw new InvalidOperationException("ETA transmission processor code is required.");
        }

        if (command.BatchSize is < 1 or > 100)
        {
            throw new InvalidOperationException("ETA transmission batch size must be between 1 and 100.");
        }

        var endpoint = ResolveActiveEtaEndpoint();
        var pending = _dbContext.EtaTransmissionAttempts
            .Where(entry => (entry.StatusCode == "QUEUED" || entry.StatusCode == "RETRY") && entry.AvailableAtUtc <= DateTime.UtcNow)
            .OrderBy(entry => entry.AvailableAtUtc)
            .ThenBy(entry => entry.Id)
            .Take(command.BatchSize)
            .ToArray();

        foreach (var transmission in pending)
        {
            var submission = _dbContext.EtaSubmissionLogs.AsNoTracking().Single(entry => entry.Id == transmission.EtaSubmissionLogId);
            var signature = EtaAdapterSecurity.ComputeSignature(
                endpoint.CredentialReference ?? string.Empty,
                transmission.PayloadText,
                submission.SubmissionReference ?? transmission.TransmissionNumber);

            var signedPayloadText = JsonSerializer.Serialize(new
            {
                submission.SubmissionReference,
                transmission.PayloadText,
                signature
            });

            transmission.Dispatch(
                processorCode,
                signature,
                signedPayloadText,
                adapterReference: $"ETA-ADP-{transmission.Id:D6}",
                responseText: JsonSerializer.Serialize(new
                {
                    dispatched = true,
                    endpoint = endpoint.EndpointCode
                }));

            _dbContext.EtaDocumentEvents.Add(new EtaDocumentEvent(
                accountsReceivableInvoiceId: transmission.AccountsReceivableInvoiceId,
                etaSubmissionLogId: transmission.EtaSubmissionLogId,
                eventType: "TRANSPORT",
                eventStatus: "DISPATCHED",
                eventTimestampUtc: DateTime.UtcNow,
                actorUserId: 1L,
                payloadText: transmission.SignedPayloadText,
                responseText: transmission.ResponseText,
                notesText: $"ETA transmission {transmission.TransmissionNumber} dispatched through endpoint {endpoint.EndpointCode}."));

            OutboxEventWriter.Enqueue(
                _dbContext,
                sourceModuleCode: "ETA",
                aggregateType: "ETA_TRANSMISSION",
                aggregateId: transmission.Id,
                eventType: "ETA_TRANSMISSION_DISPATCHED",
                payload: new
                {
                    transmission.TransmissionNumber,
                    submission.SubmissionReference,
                    endpoint.EndpointCode,
                    transmission.AdapterReference
                },
                actorUserId: 1L,
                correlationKey: submission.SubmissionReference);
        }

        _dbContext.SaveChanges();

        var views = pending.Select(entry => GetTransmissions(entry.AccountsReceivableInvoiceId).Single(item => item.EtaTransmissionAttemptId == entry.Id)).ToArray();
        return new DispatchEtaTransmissionBatchResultView(
            processorCode,
            command.BatchSize,
            views.Length,
            views);
    }

    public PollEtaTransmissionBatchResultView PollDispatchedTransmissions(PollEtaTransmissionBatchCommand command)
    {
        var processorCode = command.ProcessorCode?.Trim().ToUpperInvariant();
        if (string.IsNullOrWhiteSpace(processorCode))
        {
            throw new InvalidOperationException("ETA transmission polling processor code is required.");
        }

        if (command.BatchSize is < 1 or > 100)
        {
            throw new InvalidOperationException("ETA transmission polling batch size must be between 1 and 100.");
        }

        var dispatched = _dbContext.EtaTransmissionAttempts
            .Where(entry => entry.StatusCode == "DISPATCHED")
            .OrderBy(entry => entry.DispatchedAtUtc)
            .ThenBy(entry => entry.Id)
            .Take(command.BatchSize)
            .ToArray();

        foreach (var transmission in dispatched)
        {
            var submission = _dbContext.EtaSubmissionLogs.Single(entry => entry.Id == transmission.EtaSubmissionLogId);
            var invoice = _dbContext.AccountsReceivableInvoices.Single(entry => entry.Id == transmission.AccountsReceivableInvoiceId);
            var etaUuid = $"ETA-{transmission.Id:D6}";
            var pollResponse = JsonSerializer.Serialize(new
            {
                polled = true,
                transmission = transmission.TransmissionNumber,
                status = "ACCEPTED",
                etaUuid
            });

            transmission.PollAccepted(processorCode, etaUuid, pollResponse);
            submission.RecordResponse(
                statusCode: "ACCEPTED",
                etaUuid: etaUuid,
                responseText: pollResponse,
                errorCode: null,
                errorMessage: null,
                actorUserId: 1L);
            invoice.MarkEta(etaUuid, "ACCEPTED", 1L);

            _dbContext.EtaDocumentEvents.Add(new EtaDocumentEvent(
                accountsReceivableInvoiceId: transmission.AccountsReceivableInvoiceId,
                etaSubmissionLogId: transmission.EtaSubmissionLogId,
                eventType: "POLLING",
                eventStatus: "ACCEPTED",
                eventTimestampUtc: DateTime.UtcNow,
                actorUserId: 1L,
                payloadText: null,
                responseText: pollResponse,
                notesText: $"ETA transmission {transmission.TransmissionNumber} polled and accepted by adapter."));

            OutboxEventWriter.Enqueue(
                _dbContext,
                sourceModuleCode: "ETA",
                aggregateType: "ETA_TRANSMISSION",
                aggregateId: transmission.Id,
                eventType: "ETA_TRANSMISSION_POLLED",
                payload: new
                {
                    transmission.TransmissionNumber,
                    transmission.AdapterReference,
                    transmission.StatusCode,
                    EtaUuid = etaUuid
                },
                actorUserId: 1L,
                correlationKey: submission.SubmissionReference ?? transmission.TransmissionNumber);
        }

        _dbContext.SaveChanges();

        var views = dispatched.Select(entry => GetTransmission(entry.Id)!).ToArray();
        return new PollEtaTransmissionBatchResultView(
            processorCode,
            command.BatchSize,
            views.Length,
            views);
    }

    public EtaTransmissionAttemptView MarkTransmissionAccepted(long etaTransmissionAttemptId, long actorUserId, string processorCode, string etaUuid, string? responseText)
    {
        var transmission = _dbContext.EtaTransmissionAttempts.Single(entry => entry.Id == etaTransmissionAttemptId);
        var submission = _dbContext.EtaSubmissionLogs.Single(entry => entry.Id == transmission.EtaSubmissionLogId);
        var invoice = _dbContext.AccountsReceivableInvoices.Single(entry => entry.Id == transmission.AccountsReceivableInvoiceId);

        transmission.PollAccepted(processorCode, etaUuid, responseText);
        submission.RecordResponse("ACCEPTED", etaUuid, responseText, null, null, actorUserId);
        invoice.MarkEta(etaUuid, "ACCEPTED", actorUserId);

        _dbContext.EtaDocumentEvents.Add(new EtaDocumentEvent(
            accountsReceivableInvoiceId: transmission.AccountsReceivableInvoiceId,
            etaSubmissionLogId: transmission.EtaSubmissionLogId,
            eventType: "POLLING",
            eventStatus: "ACCEPTED",
            eventTimestampUtc: DateTime.UtcNow,
            actorUserId: actorUserId,
            payloadText: null,
            responseText: responseText,
            notesText: $"ETA authority status accepted transmission {transmission.TransmissionNumber}."));

        OutboxEventWriter.Enqueue(
            _dbContext,
            sourceModuleCode: "ETA",
            aggregateType: "ETA_TRANSMISSION",
            aggregateId: transmission.Id,
            eventType: "ETA_TRANSMISSION_POLLED",
            payload: new
            {
                transmission.TransmissionNumber,
                transmission.AdapterReference,
                transmission.StatusCode,
                EtaUuid = etaUuid
            },
            actorUserId: actorUserId,
            correlationKey: submission.SubmissionReference ?? transmission.TransmissionNumber);

        _dbContext.SaveChanges();
        return GetTransmission(etaTransmissionAttemptId)!;
    }

    public EtaTransmissionAttemptView MarkTransmissionRejected(long etaTransmissionAttemptId, long actorUserId, string processorCode, string errorText, string? responseText, string? errorCode)
    {
        var transmission = _dbContext.EtaTransmissionAttempts.Single(entry => entry.Id == etaTransmissionAttemptId);
        var submission = _dbContext.EtaSubmissionLogs.Single(entry => entry.Id == transmission.EtaSubmissionLogId);
        var invoice = _dbContext.AccountsReceivableInvoices.Single(entry => entry.Id == transmission.AccountsReceivableInvoiceId);

        transmission.PollRejected(processorCode, errorText, responseText);
        submission.RecordResponse("REJECTED", null, responseText, errorCode, errorText, actorUserId);
        invoice.MarkEta(null, "REJECTED", actorUserId);

        _dbContext.EtaDocumentEvents.Add(new EtaDocumentEvent(
            accountsReceivableInvoiceId: transmission.AccountsReceivableInvoiceId,
            etaSubmissionLogId: transmission.EtaSubmissionLogId,
            eventType: "POLLING",
            eventStatus: "REJECTED",
            eventTimestampUtc: DateTime.UtcNow,
            actorUserId: actorUserId,
            payloadText: null,
            responseText: responseText,
            notesText: $"ETA authority status rejected transmission {transmission.TransmissionNumber}: {errorText}"));

        OutboxEventWriter.Enqueue(
            _dbContext,
            sourceModuleCode: "ETA",
            aggregateType: "ETA_TRANSMISSION",
            aggregateId: transmission.Id,
            eventType: "ETA_TRANSMISSION_REJECTED",
            payload: new
            {
                transmission.TransmissionNumber,
                transmission.AdapterReference,
                transmission.StatusCode,
                ErrorCode = errorCode,
                ErrorText = errorText
            },
            actorUserId: actorUserId,
            correlationKey: submission.SubmissionReference ?? transmission.TransmissionNumber);

        _dbContext.SaveChanges();
        return GetTransmission(etaTransmissionAttemptId)!;
    }

    public EtaTransmissionAttemptView FailTransmission(long etaTransmissionAttemptId, long actorUserId, string errorText, string? responseText)
    {
        if (string.IsNullOrWhiteSpace(errorText))
        {
            throw new InvalidOperationException("ETA transmission failure reason is required.");
        }

        var transmission = _dbContext.EtaTransmissionAttempts.Single(entry => entry.Id == etaTransmissionAttemptId);
        if (transmission.StatusCode is "ACCEPTED" or "REJECTED")
        {
            throw new InvalidOperationException("Completed ETA transmissions cannot be failed.");
        }

        transmission.MarkFailed(errorText, responseText);

        _dbContext.EtaDocumentEvents.Add(new EtaDocumentEvent(
            accountsReceivableInvoiceId: transmission.AccountsReceivableInvoiceId,
            etaSubmissionLogId: transmission.EtaSubmissionLogId,
            eventType: "TRANSPORT",
            eventStatus: "FAILED",
            eventTimestampUtc: DateTime.UtcNow,
            actorUserId: actorUserId,
            payloadText: null,
            responseText: responseText,
            notesText: $"ETA transmission {transmission.TransmissionNumber} failed during live authority submission."));

        var submission = _dbContext.EtaSubmissionLogs.AsNoTracking().Single(entry => entry.Id == transmission.EtaSubmissionLogId);
        OutboxEventWriter.Enqueue(
            _dbContext,
            sourceModuleCode: "ETA",
            aggregateType: "ETA_TRANSMISSION",
            aggregateId: transmission.Id,
            eventType: "ETA_TRANSMISSION_FAILED",
            payload: new
            {
                transmission.TransmissionNumber,
                submission.SubmissionReference,
                transmission.LastErrorText
            },
            actorUserId: actorUserId,
            correlationKey: submission.SubmissionReference ?? transmission.TransmissionNumber);

        _dbContext.SaveChanges();
        return GetTransmission(etaTransmissionAttemptId)!;
    }

    public EtaTransmissionAttemptView RetryTransmission(long etaTransmissionAttemptId, RetryEtaTransmissionCommand command)
    {
        if (command.DelaySeconds is < 0 or > 86400)
        {
            throw new InvalidOperationException("ETA transmission retry delay must be between 0 and 86400 seconds.");
        }

        var transmission = _dbContext.EtaTransmissionAttempts.Single(entry => entry.Id == etaTransmissionAttemptId);
        if (transmission.StatusCode != "FAILED")
        {
            throw new InvalidOperationException("Only failed ETA transmissions can be retried.");
        }

        transmission.ScheduleRetry(command.RetryReasonText, DateTime.UtcNow.AddSeconds(command.DelaySeconds));
        transmission.Touch(command.ActorUserId);
        _dbContext.SaveChanges();

        var endpointCode = _dbContext.IntegrationEndpointRegistryEntries.AsNoTracking()
            .Where(entry => entry.Id == transmission.IntegrationEndpointId)
            .Select(entry => entry.EndpointCode)
            .SingleOrDefault() ?? $"INTEP-MISSING-{transmission.IntegrationEndpointId:D6}";
        return Map(transmission, endpointCode);
    }

    private static EtaSubmissionLogView Map(EtaSubmissionLog log) =>
        new(
            log.Id,
            log.AccountsReceivableInvoiceId,
            log.SubmissionReference,
            log.EtaUuid,
            log.StatusCode,
            log.SubmittedAtUtc,
            log.ResponseAtUtc,
            log.ErrorCode,
            log.ErrorMessage,
            log.PayloadText,
            log.ResponseText);

    private static EtaTransmissionAttemptView Map(EtaTransmissionAttempt transmission, string endpointCode) =>
        new(
            transmission.Id,
            transmission.AccountsReceivableInvoiceId,
            transmission.EtaSubmissionLogId,
            transmission.IntegrationEndpointId,
            endpointCode,
            transmission.TransmissionNumber,
            transmission.StatusCode,
            transmission.PayloadText,
            transmission.SignedPayloadText,
            transmission.SignatureText,
            transmission.AdapterReference,
            transmission.ResponseText,
            transmission.AttemptNumber,
            transmission.AvailableAtUtc,
            transmission.DispatchedAtUtc,
            transmission.DispatchedByProcess,
            transmission.PolledAtUtc,
            transmission.PolledByProcess,
            transmission.LastErrorText,
            transmission.CreatedAtUtc);

    private EtaPayloadPreviewView BuildPayloadPreview(Domain.Finance.AccountsReceivableInvoice invoice)
    {
        var customer = _dbContext.BusinessParties
            .AsNoTracking()
            .SingleOrDefault(existingParty => existingParty.Id == invoice.CustomerId);
        var branch = ResolveBranch(invoice);
        var buildResult = EtaPayloadBuilder.Build(invoice, customer, branch);

        return new EtaPayloadPreviewView(
            invoice.Id,
            invoice.InvoiceNumber,
            buildResult.NormalizedDocumentType,
            buildResult.PayloadText,
            buildResult.IsStatutoryReady,
            buildResult.ValidationMessages);
    }

    private Domain.Integration.IntegrationEndpointRegistryEntry ResolveActiveEtaEndpoint()
    {
        var endpoint = _dbContext.IntegrationEndpointRegistryEntries
            .AsNoTracking()
            .Where(entry =>
                entry.IsActive
                && entry.DirectionCode == "OUTBOUND"
                && entry.TransportCode == "REST"
                && entry.HttpMethodCode == "POST"
                && entry.IntegrationProviderCode == "ETA"
                && !string.IsNullOrWhiteSpace(entry.CredentialReference)
                && entry.RelativePath != null
                && entry.RelativePath.Contains("/documentsubmissions"))
            .OrderByDescending(entry => entry.Id)
            .FirstOrDefault()
            ?? throw new InvalidOperationException("ETA transmission requires an active outbound ETA integration endpoint.");

        if (string.IsNullOrWhiteSpace(endpoint.CredentialReference))
        {
            throw new InvalidOperationException("ETA transmission requires a credential reference for signing.");
        }

        return endpoint;
    }

    private Domain.Organization.Branch? ResolveBranch(Domain.Finance.AccountsReceivableInvoice invoice)
    {
        long? branchId = null;

        if (invoice.DispatchOrderId.HasValue)
        {
            branchId = _dbContext.DispatchOrders
                .AsNoTracking()
                .Where(dispatch => dispatch.Id == invoice.DispatchOrderId.Value)
                .Select(dispatch => (long?)dispatch.BranchId)
                .SingleOrDefault();
        }

        if (!branchId.HasValue && invoice.SalesOrderId.HasValue)
        {
            branchId = _dbContext.SalesOrders
                .AsNoTracking()
                .Where(order => order.Id == invoice.SalesOrderId.Value)
                .Select(order => (long?)order.BranchId)
                .SingleOrDefault();
        }

        return !branchId.HasValue
            ? null
            : _dbContext.Branches.AsNoTracking().SingleOrDefault(branch => branch.Id == branchId.Value);
    }

    private static EtaDocumentEventView Map(EtaDocumentEvent evt) =>
        new(
            evt.Id,
            evt.AccountsReceivableInvoiceId,
            evt.EtaSubmissionLogId,
            evt.EventType,
            evt.EventStatus,
            evt.EventTimestampUtc,
            evt.ActorUserId,
            evt.PayloadText,
            evt.ResponseText,
            evt.NotesText);
}
