using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Breez.Sdk.Spark;
using BTCPayServer.Lightning;
using NBitcoin;
using Network = Breez.Sdk.Spark.Network;

namespace BTCPayServer.Plugins.Breez;

public class BreezLightningClient : ILightningClient, IDisposable
{
    public override string ToString()
    {
        return $"type=breez;key={PaymentKey}";
    }

    private readonly NBitcoin.Network _network;
    public readonly string PaymentKey;

    public ConcurrentQueue<(DateTimeOffset timestamp, string log)> Events { get; set} = new();

    private BreezSdk _sdk;

    public static async Task<BreezLightningClient> Create(string apiKey, string workingDir, NBitcoin.Network network,
        Mnemonic mnemonic, string paymentKey)
    {
        apiKey ??= "99010c6f84541bf582899db6728f6098ba98ca95ea569f4c63f2c2c9205ace57";

        var config = BreezSdkSparkMethods.DefaultConfig(
            network == NBitcoin.Network.Main ? Network.Mainnet :
            network == NBitcoin.Network.TestNet ? Network.Testnet :
            network == NBitcoin.Network.RegTest ? Network.Regtest : Network.Signet
        ) with
        {
            apiKey = apiKey
        };

        var seed = mnemonic.DeriveSeed();
        var sdk = await BreezSdkSparkMethods.Connect(new ConnectRequest(config, seed.ToList(), workingDir));

        return new BreezLightningClient(sdk, network, paymentKey);
    }

    private BreezLightningClient(BreezSdk sdk, NBitcoin.Network network, string paymentKey)
    {
        _sdk = sdk;
        _network = network;
        PaymentKey = paymentKey;
    }

    public BreezSdk Sdk => _sdk;

    public async Task<LightningInvoice> GetInvoice(string invoiceId, CancellationToken cancellation = default)
    {
        return await GetInvoice(uint256.Parse(invoiceId), cancellation);
    }

    public async Task<LightningInvoice> GetInvoice(uint256 paymentHash, CancellationToken cancellation = default)
    {
        try
        {
            var response = await _sdk.GetPayment(new GetPaymentRequest(paymentHash.ToString()));
            if (response?.payment != null)
            {
                return FromPayment(response.payment);
            }
        }
        catch
        {
            // Payment not found
        }

        return new LightningInvoice()
        {
            Id = paymentHash.ToString(),
            PaymentHash = paymentHash.ToString(),
            Status = LightningInvoiceStatus.Unpaid
        };
    }

    public async Task<LightningInvoice[]> ListInvoices(CancellationToken cancellation = default)
    {
        return await ListInvoices(null, cancellation);
    }

    public async Task<LightningInvoice[]> ListInvoices(ListInvoicesParams request,
        CancellationToken cancellation = default)
    {
        var req = new ListPaymentsRequest(
            typeFilter: new List<PaymentType> { PaymentType.Receive },
            statusFilter: request?.PendingOnly == true ? new List<PaymentStatus> { PaymentStatus.Pending } : null,
            assetFilter: new AssetFilter.Bitcoin(),
            fromTimestamp: null,
            toTimestamp: null,
            offset: (ulong?)request?.OffsetIndex,
            limit: null,
            sortAscending: false
        );

        var response = await _sdk.ListPayments(req);
        return response.payments.Select(FromPayment).Where(p => p != null).ToArray();
    }

    public async Task<LightningPayment> GetPayment(string paymentHash, CancellationToken cancellation = default)
    {
        try
        {
            var response = await _sdk.GetPayment(new GetPaymentRequest(paymentHash));
            return ToLightningPayment(response?.payment);
        }
        catch
        {
            return null;
        }
    }

    public async Task<LightningPayment[]> ListPayments(CancellationToken cancellation = default)
    {
        return await ListPayments(null, cancellation);
    }

    public async Task<LightningPayment[]> ListPayments(ListPaymentsParams request,
        CancellationToken cancellation = default)
    {
        var req = new ListPaymentsRequest(
            typeFilter: new List<PaymentType> { PaymentType.Send },
            statusFilter: null,
            assetFilter: new AssetFilter.Bitcoin(),
            fromTimestamp: null,
            toTimestamp: null,
            offset: (ulong?)request?.OffsetIndex,
            limit: null,
            sortAscending: false
        );

        var response = await _sdk.ListPayments(req);
        return response.payments.Select(ToLightningPayment).Where(p => p != null).ToArray();
    }

    public async Task<LightningInvoice> CreateInvoice(LightMoney amount, string description, TimeSpan expiry,
        CancellationToken cancellation = default)
    {
        var expiryS = expiry == TimeSpan.Zero ? (ulong?)null : Math.Max(0, (ulong)expiry.TotalSeconds);
        description ??= "Invoice";

        var paymentMethod = new ReceivePaymentMethod.Bolt11Invoice(description, (ulong)amount.ToUnit(LightMoneyUnit.Satoshi));
        var response = await _sdk.ReceivePayment(new ReceivePaymentRequest(paymentMethod));

        return FromReceivePaymentResponse(response);
    }

    public async Task<LightningInvoice> CreateInvoice(CreateInvoiceParams createInvoiceRequest,
        CancellationToken cancellation = default)
    {
        var expiryS = createInvoiceRequest.Expiry == TimeSpan.Zero
            ? (ulong?)null
            : Math.Max(0, (ulong)createInvoiceRequest.Expiry.TotalSeconds);

        var description = createInvoiceRequest.Description ?? createInvoiceRequest.DescriptionHash?.ToString() ?? "Invoice";
        var paymentMethod = new ReceivePaymentMethod.Bolt11Invoice(description, (ulong)createInvoiceRequest.Amount.ToUnit(LightMoneyUnit.Satoshi));
        var response = await _sdk.ReceivePayment(new ReceivePaymentRequest(paymentMethod));

        return FromReceivePaymentResponse(response);
    }

    public async Task<ILightningInvoiceListener> Listen(CancellationToken cancellation = default)
    {
        return new BreezInvoiceListener(this, cancellation);
    }

    public async Task<LightningNodeInformation> GetInfo(CancellationToken cancellation = default)
    {
        var response = await _sdk.GetInfo(new GetInfoRequest(ensureSynced: false));

        return new LightningNodeInformation()
        {
            Alias = "Breez Spark (nodeless)",
            BlockHeight = 0
        };
    }

    public async Task<LightningNodeBalance> GetBalance(CancellationToken cancellation = default)
    {
        var response = await _sdk.GetInfo(new GetInfoRequest(ensureSynced: false));

        return new LightningNodeBalance()
        {
            OnchainBalance = new OnchainBalance()
            {
                Confirmed = Money.Zero
            },
            OffchainBalance = new OffchainBalance()
            {
                Local = LightMoney.Satoshis((long)response.balanceSats),
                Remote = LightMoney.Zero
            }
        };
    }

    public async Task<PayResponse> Pay(PayInvoiceParams payParams, CancellationToken cancellation = default)
    {
        return await Pay(null, payParams, cancellation);
    }

    public async Task<PayResponse> Pay(string bolt11, PayInvoiceParams payParams,
        CancellationToken cancellation = default)
    {
        try
        {
            if (string.IsNullOrEmpty(bolt11))
            {
                return new PayResponse(PayResult.Error, "BOLT11 invoice required");
            }

            var prepareRequest = new PrepareSendPaymentRequest(
                new SendPaymentDestination.Bolt11Invoice(bolt11, null, false)
            );
            var prepareResponse = await _sdk.PrepareSendPayment(prepareRequest);

            var sendRequest = new SendPaymentRequest(
                prepareResponse,
                new SendPaymentOptions.Bolt11Invoice(preferSpark: false, completionTimeoutSecs: 30)
            );
            var sendResponse = await _sdk.SendPayment(sendRequest);

            return new PayResponse()
            {
                Result = sendResponse.payment.status switch
                {
                    PaymentStatus.Failed => PayResult.Error,
                    PaymentStatus.Completed => PayResult.Ok,
                    PaymentStatus.Pending => PayResult.Unknown,
                    _ => PayResult.Error
                },
                Details = new PayDetails()
                {
                    Status = sendResponse.payment.status switch
                    {
                        PaymentStatus.Failed => LightningPaymentStatus.Failed,
                        PaymentStatus.Completed => LightningPaymentStatus.Complete,
                        PaymentStatus.Pending => LightningPaymentStatus.Pending,
                        _ => LightningPaymentStatus.Unknown
                    },
                    TotalAmount = LightMoney.Satoshis((long)sendResponse.payment.amount),
                    FeeAmount = (long)sendResponse.payment.fees
                }
            };
        }
        catch (Exception e)
        {
            return new PayResponse(PayResult.Error, e.Message);
        }
    }

    public async Task<PayResponse> Pay(string bolt11, CancellationToken cancellation = default)
    {
        return await Pay(bolt11, null, cancellation);
    }

    public async Task<OpenChannelResponse> OpenChannel(OpenChannelRequest openChannelRequest,
        CancellationToken cancellation = default)
    {
        throw new NotImplementedException();
    }

    public async Task<BitcoinAddress> GetDepositAddress(CancellationToken cancellation = default)
    {
        throw new NotImplementedException();
    }

    public async Task<ConnectionResult> ConnectTo(NodeInfo nodeInfo, CancellationToken cancellation = default)
    {
        throw new NotImplementedException();
    }

    public async Task CancelInvoice(string invoiceId, CancellationToken cancellation = default)
    {
        throw new NotImplementedException();
    }

    public async Task<LightningChannel[]> ListChannels(CancellationToken cancellation = default)
    {
        throw new NotImplementedException();
    }

    private LightningInvoice FromReceivePaymentResponse(ReceivePaymentResponse response)
    {
        return new LightningInvoice()
        {
            BOLT11 = response.paymentRequest,
            Status = LightningInvoiceStatus.Unpaid,
            Amount = LightMoney.Satoshis((long)response.fee)
        };
    }

    private LightningInvoice FromPayment(Payment payment)
    {
        if (payment == null) return null;

        string paymentHash = null;
        string bolt11 = null;

        if (payment.details is PaymentDetails.Lightning lightningDetails)
        {
            paymentHash = lightningDetails.paymentHash;
            bolt11 = lightningDetails.invoice;
        }

        return new LightningInvoice()
        {
            Id = payment.id,
            PaymentHash = paymentHash ?? payment.id,
            BOLT11 = bolt11,
            Amount = LightMoney.Satoshis((long)payment.amount),
            Status = payment.status switch
            {
                PaymentStatus.Pending => LightningInvoiceStatus.Unpaid,
                PaymentStatus.Failed => LightningInvoiceStatus.Expired,
                PaymentStatus.Completed => LightningInvoiceStatus.Paid,
                _ => LightningInvoiceStatus.Unpaid
            },
            PaidAt = DateTimeOffset.FromUnixTimeSeconds((long)payment.timestamp)
        };
    }

    private LightningPayment ToLightningPayment(Payment payment)
    {
        if (payment == null) return null;

        string paymentHash = null;
        string preimage = null;
        string bolt11 = null;

        if (payment.details is PaymentDetails.Lightning lightningDetails)
        {
            paymentHash = lightningDetails.paymentHash;
            preimage = lightningDetails.preimage;
            bolt11 = lightningDetails.invoice;
        }

        return new LightningPayment()
        {
            Id = payment.id,
            PaymentHash = paymentHash ?? payment.id,
            Preimage = preimage,
            BOLT11 = bolt11,
            Amount = LightMoney.Satoshis((long)payment.amount),
            Status = payment.status switch
            {
                PaymentStatus.Failed => LightningPaymentStatus.Failed,
                PaymentStatus.Completed => LightningPaymentStatus.Complete,
                PaymentStatus.Pending => LightningPaymentStatus.Pending,
                _ => LightningPaymentStatus.Unknown
            },
            CreatedAt = DateTimeOffset.FromUnixTimeSeconds((long)payment.timestamp),
            Fee = LightMoney.Satoshis((long)payment.fees),
            AmountSent = LightMoney.Satoshis((long)payment.amount)
        };
    }

    public void Dispose()
    {
        _sdk?.Dispose();
    }

    public class BreezInvoiceListener : ILightningInvoiceListener
    {
        private readonly BreezLightningClient _breezLightningClient;
        private readonly CancellationToken _cancellationToken;
        private readonly ConcurrentQueue<Payment> _invoices = new();

        public BreezInvoiceListener(BreezLightningClient breezLightningClient, CancellationToken cancellationToken)
        {
            _breezLightningClient = breezLightningClient;
            _cancellationToken = cancellationToken;
        }

        public void Dispose()
        {
        }

        public async Task<LightningInvoice> WaitInvoice(CancellationToken cancellation)
        {
            while (!cancellation.IsCancellationRequested)
            {
                if (_invoices.TryDequeue(out var payment))
                {
                    return _breezLightningClient.FromPayment(payment);
                }

                await Task.Delay(100, cancellation);
            }

            cancellation.ThrowIfCancellationRequested();
            return null;
        }
    }
}
