@using System.Security.Claims
@using System.Threading
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Client
@using BTCPayServer.Configuration
@using BTCPayServer.Data
@using BTCPayServer.Payments
@using BTCPayServer.PayoutProcessors
@using BTCPayServer.Payouts
@using BTCPayServer.Services

@using BTCPayServer.Services.Stores
@using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Identity
@using Microsoft.AspNetCore.Routing
@using NBitcoin
@implements IAsyncDisposable;

@code {
    private BringinService.BringinStoreSettings? _settings;
    private bool _isLoaded = false;
    private CancellationTokenSource _cts = new CancellationTokenSource();


    [Inject] BTCPayServerOptions BTCPayServerOptions { get; set; }
    [Inject] private AuthenticationStateProvider AuthenticationStateProvider { get; set; }
    [Inject] UserManager<ApplicationUser> UserManager { get; set; }
    [Inject] private DisplayFormatter DisplayFormatter { get; set; }
    [Inject] private BringinService BringinService { get; set; }
    [Inject] private LinkGenerator LinkGenerator { get; set; }
    [Inject] private StoreRepository StoreRepository { get; set; }
    [Inject] private BTCPayNetworkProvider BTCPayNetworkProvider { get; set; }
    [Inject] private IHttpClientFactory HttpClientFactory { get; set; }
    [Inject] private PayoutProcessorService PayoutProcessorService { get; set; }
    [Inject] private IAuthorizationService AuthorizationService { get; set; }
    [Inject] private PayoutMethodHandlerDictionary PayoutMethodHandlerDictionary { get; set; }
    [Parameter] public string StoreId { get; set; }
    private decimal? LastFiatBalance { get; set; }
    private DateTimeOffset? LastDataFetch { get; set; }
    private decimal? LastFiatRate { get; set; }
    private string SaveError { get; set; }

    private bool ApiKeyError
    {
        get => _apiKeyError;
        set
        {
            if (_apiKeyError == value)
                return;
            _apiKeyError = value;
            InvokeAsync(StateHasChanged);
        }
    }

    private bool IsLoaded
    {
        get => _isLoaded;
        set
        {
            if (_isLoaded == value)
                return;
            _isLoaded = value;
            InvokeAsync(StateHasChanged);
        }
    }

    private bool EditMode
    {
        get => _editMode;
        set
        {
            if (_editMode == value)
                return;
            _editMode = value;
            InvokeAsync(StateHasChanged);
        }
    }

    private bool _editMode;
    private bool _readOnly;

    async Task<ClaimsPrincipal> GetUser()
    {
        var state = await AuthenticationStateProvider.GetAuthenticationStateAsync();
        if (state.User.Identity?.IsAuthenticated is not true)
            return null;
        return state.User;
    }
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            _readOnly = !(await AuthorizationService.AuthorizeAsync(await GetUser(), StoreId, Policies.CanModifyStoreSettings)).Succeeded;
            OnboardLink = LinkGenerator.GetPathByAction("Onboard", "Bringin", new {StoreId}, pathBase: BTCPayServerOptions.RootPath);
            _settings = BringinService.IsInEditMode(StoreId) ? await BringinService.Update(StoreId) : await BringinService.Get(StoreId);
            var store = await StoreRepository.FindStore(StoreId);
            _pms = PayoutMethodHandlerDictionary.GetSupportedPayoutMethods(store);
            _pps = (await PayoutProcessorService.GetProcessors(new PayoutProcessorService.PayoutProcessorQuery()
            {
                Stores = new[] {StoreId},
                PayoutMethods = _pms.Select(p => p).ToArray()
            })).Select(data => PayoutMethodId.TryParse(data.PayoutMethodId)).Where(id => id is not null).ToArray();
            EditMode = BringinService.IsInEditMode(StoreId);
            IsLoaded = true;
            _ = FetchBalanceAndRate();
            BringinService.EditModeChanged += EditModeChanged;
        }

        await base.OnAfterRenderAsync(firstRender);
    }

    private void EditModeChanged(object sender, (string storeId, bool editMode) e)
    {
        if (e.storeId != StoreId)
        {
            return;
        }

        if (EditMode == e.editMode)
            return;
        _ = e.editMode ? Edit() : CancelEdit();
        InvokeAsync(StateHasChanged);
    }

    private async Task Edit()
    {
        if (_saving)
            return;
        SaveError = null;
        ApiKeyError = false;
        _settings = await BringinService.Update(StoreId);
        await TestApiKey();
        EditMode = true;
    }

    private async Task CancelEdit()
    {
        if (_saving)
            return;
        SaveError = null;
        await BringinService.CancelEdit(StoreId);
        EditMode = false;
        _settings = await BringinService.Get(StoreId);
    }

    private async Task TestApiKey()
    {
        ApiKeyError = false;
        if (string.IsNullOrEmpty(_settings?.ApiKey))
            return;
        try
        {
            var network = BTCPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
            var userId = await _settings.CreateClient(HttpClientFactory, network.NBitcoinNetwork).GetUserId();
            ApiKeyError = string.IsNullOrEmpty(userId);
        }
        catch (Exception e)
        {
            ApiKeyError = true;
        }
    }

    private bool _saving;
    private HashSet<PayoutMethodId> _pms;
    private PayoutMethodId[] _pps;
    private bool _apiKeyError;

    private async Task Save()
    {
        if (_saving)
            return;
        try
        {
            _saving = true;
            await TestApiKey();
            if (ApiKeyError)
                return;
            SaveError = null;
            if (!EditMode)
                return;
            await BringinService.Update(StoreId, _settings);
            EditMode = false;
            fetcherCTS?.Cancel();
        }
        finally
        {
            _saving = false;
        }
    }

    CancellationTokenSource fetcherCTS;


    private async Task FetchBalanceAndRate()
    {
        if (_cts.IsCancellationRequested)
            return;
        fetcherCTS = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token);
        try
        {
            if (_settings?.ApiKey is null || EditMode)
                return;

            var network = BTCPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
            var client = _settings.CreateClient(HttpClientFactory, network.NBitcoinNetwork);
            LastFiatBalance = await client.GetFiatBalance();
            LastFiatRate = (await client.GetRate()).BringinPrice;
            LastDataFetch = DateTimeOffset.UtcNow;
            LastTxs = await client.GetTransactions();
            _ = InvokeAsync(StateHasChanged);
        }
        finally
        {
            try
            {
                await Task.Delay(TimeSpan.FromMinutes(1), fetcherCTS.Token);
            }
            catch
            {
            }

            await FetchBalanceAndRate();
        }
    }

    public BringinClient.GetTransactionListResponse LastTxs { get; set; }


    private void UpdateDestinationValue(BringinService.BringinStoreSettings.PaymentMethodSettings settings, object eValue)
    {
        var newValue = Math.Min(100, Math.Round(Convert.ToDecimal(eValue), 2));
        settings.PercentageToForward = newValue;
    }

    public async ValueTask DisposeAsync()
    {
        await _cts.CancelAsync();
        BringinService.EditModeChanged -= EditModeChanged;
    }

    public string ApiKey
    {
        get => _settings.ApiKey;
        set
        {
            if (_settings.ApiKey == value)
                return;
            _settings.ApiKey = value;
            InvokeAsync(StateHasChanged);
        }
    }

    public string TimeAgo
    {
        get
        {
            var res = LastDataFetch is null ? "" : LastDataFetch.Value.ToTimeAgo();

            Task.Delay(TimeSpan.FromSeconds(1)).ContinueWith(_ => InvokeAsync(StateHasChanged));

            return res;
        }
    }

    private Task CreateManual()
    {
        SaveError = null;
        _manualOrder = true;
        return InvokeAsync(StateHasChanged);
    }

    private Task CancelManual()
    {
        SaveError = null;
        _manualOrder = false;
        ManualOrderResult = null;
        return InvokeAsync(StateHasChanged);
    }

    public async Task SubmitOrder()
    {
        _saving = true;

        await InvokeAsync(StateHasChanged);
        var pm = PayoutMethodId.TryParse(ManualOrderPaymentMethod);
        if (pm is null)
        {
            SaveError = "Invalid payment method";
            return;
        }

        try
        {
            SaveError = null;
            ManualOrderResult = await BringinService.CreateOrder(StoreId, pm, Money.Coins(ManualOrderAmount.Value), true);
        }
        catch (Exception e)
        {
            SaveError = e.Message;
            _saving = false;
        }

        await InvokeAsync(StateHasChanged);
    }

    private string ManualOrderResult = null;

    private bool _manualOrder = false;
    private string ManualOrderPaymentMethod = null;
    private decimal? ManualOrderAmount = null;

    // private bool ManualOrderPayout = true;

    public string OnboardLink;

    private async void ResetBalance(PaymentMethodId pmi)
    {
        if (_saving)
            return;
        try
        {
            _saving = true;
            await BringinService.ResetBalance(StoreId, pmi);
        }
        finally
        {
            _saving = false;
        }
    }

}


<div class="widget store-wallet-balance" id="Bringin-Info">
    @if (!IsLoaded)
    {
        <h2 class="text-muted">Loading Bringin offramp</h2>
    }
    else
    {
        <header>
            <h4 class="text-muted">
                Bringin Off-ramp
                @if (EditMode)
                {
                    <span class="btcpay-status btcpay-status--pending"></span>
                }
                else if (_settings?.Enabled is true)
                {
                    <span class="btcpay-status btcpay-status--enabled"></span>
                }
                else if (_settings is not null)
                {
                    <span class="btcpay-status btcpay-status--disabled"></span>
                }

            </h4>

            @if (!_readOnly)
            {
                <div class="d-flex gap-2">
                    @if (_manualOrder)
                    {
                        <button class="btn  btn-sm btn-outline-secondary" @onclick="CancelManual" disabled="@_saving">Cancel order</button>
                    }
                    else if (_settings is not null && !EditMode)
                    {
                        <button class="btn btn-link" @onclick="Edit">Edit</button>
                        <button class="btn btn-link" @onclick="CreateManual">Manual order</button>
                    }
                    else if (_settings is not null && EditMode)
                    {
                        if (ApiKeyError)
                        {
                            <button class="btn btn-sm btn-outline-secondary" @onclick="TestApiKey" disabled="@_saving">Test API Key</button>
                        }

                        if (!string.IsNullOrEmpty(_settings.ApiKey) && !ApiKeyError)
                        {
                            <button class="btn btn-sm btn-primary" @onclick="Save" disabled="@_saving">Save</button>
                        }

                        <button class="btn  btn-sm btn-outline-secondary" @onclick="CancelEdit" disabled="@_saving">Cancel edit</button>
                    }
                </div>
            }
        </header>


        @if (_settings is null)
        {
            <p class="text-secondary my-3">
                Bringin is a service that allows you to automatically convert your BTC to EUR and send it to your bank account. Start configuring it by clicking on the button below.
            </p>

            @if (!_readOnly)
            {
                <div class="d-flex">
                    <button class="btn btn-lg btn-outline-primary" @onclick="Edit">Configure</button>
                </div>
            }
        }
        else if (_manualOrder)
        {
            var items = new List<PayoutMethodId>();
            items.AddRange(BringinService.SupportedMethods.Where(s => _pms.Contains(s.PayoutMethod)).Select(s => s.PayoutMethod));


            <div class="row">
                <div class="col-xxl-constrain">

                    <p class="text-secondary my-3">
                        Create an order irrespective of the current balance tracked by the plugin.
                    </p>
                    @if (!string.IsNullOrEmpty(SaveError))
                    {
                        <div class="alert alert-danger">@SaveError</div>
                    }

                    @if (!string.IsNullOrEmpty(ManualOrderResult))
                    {
                        <div class="alert alert-success">Payout created: @ManualOrderResult</div>

                        <div class="form-group">
                            <button class="btn btn-primary" @onclick="CancelManual">Go back</button>
                        </div>
                    }
                    else
                    {
                        <div class="form-group">
                            @* <label class="form-label">Payment method</label> *@
                            <select @bind="ManualOrderPaymentMethod" class="form-select">
                                <option value="">Select a payment method</option>
                                @foreach (var opt in items)
                                {
                                    <option value="@opt.ToString()">@opt</option>
                                }
                            </select>
                        </div>
                        @if (!string.IsNullOrEmpty(ManualOrderPaymentMethod))
                        {
                            // var fiat = BringinService.SupportedMethods.First(s => s.PaymentMethod.ToString() == ManualOrderPaymentMethod)?.FiatMinimum is true;

                            <div class="form-group">
                                <label class="form-label">Amount</label>
                                <div class="input-group input-group-sm">
                                    <input type="number" @bind="ManualOrderAmount" min="" class="form-control form-control-sm"/>
                                    <span class="input-group-text">BTC</span>
                                </div>
                            </div>

                            @* <div class="form-group"> *@
                            @*     <div class="d-flex align-items-center"> *@
                            @*         <input type="checkbox" class="btcpay-toggle me-2" @bind="ManualOrderPayout"/> *@
                            @*         <label class="form-label mb-0 me-1">Create as Payout</label> *@
                            @*     </div> *@
                            @* </div> *@

                            <div class="form-group">
                                <button class="btn btn-primary" disabled="@(ManualOrderAmount is null or <= 0 || string.IsNullOrEmpty(ManualOrderPaymentMethod) || _saving)" @onclick="SubmitOrder">Create order</button>
                            </div>
                        }
                    }
                </div>
            </div>
        }
        else if (!EditMode || _readOnly)
        {
            @if (LastFiatBalance is not null)
            {
                <div class="store-number">
                    <header>
                        <h6>Balance</h6>
                    </header>
                    <div class="balance d-flex align-items-baseline gap-1">
                        <h3 class="d-inline-block me-1" data-balance="LastFiatBalance" data-sensitive>@LastFiatBalance</h3>
                        <span class="text-secondary fw-semibold currency">EUR</span>
                        <span class="text-secondary">@TimeAgo</span>

                    </div>
                </div>
            }

            @foreach (var method in _settings.MethodSettings)
            {
                var pmi = PayoutMethodId.TryParse(method.Key);
                if (pmi is null)
                    continue;
                if (!_pms.Contains(pmi))
                    continue;
                <hr class=""/>
                <div class="store-number">
                    <header>
                        <h6>@pmi</h6>
                    </header>

                    @if (LastFiatRate is null || !method.Value.FiatThreshold)
                    {
                        <div class="balance d-flex align-items-baseline gap-1">
                            <h3 class="d-inline-block me-1" data-balance="@method.Value.CurrentBalance" data-sensitive>@DisplayFormatter.Currency(method.Value.CurrentBalance, "BTC", DisplayFormatter.CurrencyFormat.None)</h3>
                            <span class="text-secondary fw-semibold currency">BTC</span>
                            <span class="text-secondary">pending to forward once @DisplayFormatter.Currency(method.Value.Threshold, method.Value.FiatThreshold ? "EUR" : "BTC") is reached.</span>

                        </div>
                    }
                    else if (LastFiatRate is not null && method.Value.FiatThreshold)
                    {
                        var balanceInFiat = method.Value.CurrentBalance * LastFiatRate;
                        var thresholdinBtc = method.Value.Threshold / LastFiatRate;
                        var percentage = (balanceInFiat / method.Value.Threshold) * 100m;

                        <div class="balance d-flex align-items-baseline gap-1">
                            <h3 class="d-inline-block me-1" data-balance="@method.Value.CurrentBalance" data-sensitive>@DisplayFormatter.Currency(method.Value.CurrentBalance, "BTC", DisplayFormatter.CurrencyFormat.None)</h3>
                            <span class="text-secondary fw-semibold currency">BTC</span>
                            <span class="text-secondary"> (@DisplayFormatter.Currency(balanceInFiat.Value, "EUR", DisplayFormatter.CurrencyFormat.Code)) pending to forward once @DisplayFormatter.Currency(thresholdinBtc.Value, "BTC", DisplayFormatter.CurrencyFormat.Code) (@DisplayFormatter.Currency(method.Value.Threshold, "EUR")) is reached.</span>
                            @if (method.Value.CurrentBalance > 0)
                            {
                                <button class="btn btn-link" @onclick="() => ResetBalance(PaymentMethodId.Parse(pmi.ToString()))" disabled="@_saving">Reset balance</button>
                                //Clear balance
                            }
                        </div>
                    }

                    @if (method.Value.PendingPayouts.Any())
                    {
                        <div class="balance d-flex align-items-baseline gap-1">
                            <h3 class="d-inline-block me-1" data-balance="@method.Value.PendingPayouts.Count">@method.Value.PendingPayouts.Count</h3>
                            <span class="text-secondary fw-semibold">pending payouts</span>
                        </div>
                    }
                    @if (!_pps.Contains(pmi))
                    {
                        <p class="text-warning">A payout processor has not been configured for this payment method. Payouts generated by Bringin will not be automatically handled. <a href="@LinkGenerator.GetPathByAction("ConfigureStorePayoutProcessors", "UIPayoutProcessors", new {StoreId}, pathBase: BTCPayServerOptions.RootPath)">Configure now</a></p>
                    }
                </div>
            }
        }
        else
        {
            @if (!string.IsNullOrEmpty(SaveError))
            {
                <div class="alert alert-danger">@SaveError</div>
            }

            @if (string.IsNullOrEmpty(_settings.ApiKey) || ApiKeyError)
            {
                <div class="row">
                    <div class="col-xxl-constrain">
                        <div class="form-group">

                            <label class="form-label">API Key</label>
                            <input type="password" class="form-control" @bind:event="oninput" @bind="ApiKey"/>
                            <p class="my-2">You can get one <a href="@OnboardLink" target="_blank">here</a></p>
                            @if (ApiKeyError)
                            {
                                <div class="text-danger">Invalid API Key</div>
                            }
                        </div>
                    </div>
                </div>
            }
            else
            {
                <div class="row">
                    <div class="col-xxl-constrain">

                        <div class="form-group">
                            <label class="form-label">API Key</label>
                            <input type="password" class="form-control" @bind="_settings.ApiKey"/>
                            <p class="my-2">You can get one <a href="@OnboardLink" target="_blank">here</a></p>
                            @if (ApiKeyError)
                            {
                                <div class="text-danger">Invalid API Key</div>
                            }
                        </div>
                        <div class="form-group">
                            <div class="d-flex align-items-center">
                                <input type="checkbox" class="btcpay-toggle me-2" @bind="_settings.Enabled"/>
                                <label class="form-label mb-0 me-1">Enabled</label>
                            </div>
                        </div>
                    </div>
                </div>

                <div class="container">
                    <div class="row gx-5">
                        @for (int i = 0; i < _settings.MethodSettings.Count; i++)
                        {
                            var method = _settings.MethodSettings.ElementAt(i);
                            var pmId = PaymentMethodId.TryParse(method.Key);
                            if (pmId is null)
                                continue;
                            var supportedMethod = BringinService.SupportedMethods.FirstOrDefault(s => s.PayoutMethod.ToString() == method.Key);
                            <div class="col-xxl-constrain col-12 @(_settings.MethodSettings.Count > 1 ? $"col-xl-6 {(i == 0 ? "border-end" : "")}" : "")">
                                <h5 class=" border-bottom-0 text-muted  mb-4">@pmId</h5>
                                <div class="card-body">
                                    <div class="form-group">
                                        <label class="form-label">Percentage</label>
                                        <input type="range" value="@method.Value.PercentageToForward" @oninput="@((e) => UpdateDestinationValue(method.Value, e.Value))" min="0" step='0.01' class="form-range" max="100"/>
                                        <div class="input-group input-group-sm">
                                            <input type="number" step='0.01' value="@method.Value.PercentageToForward" @onchange="@((e) => UpdateDestinationValue(method.Value, e.Value))" class="form-control form-control-sm"/>
                                            <span class="input-group-text">%</span>
                                        </div>
                                        <p class="text-muted my-2">Every time an invoice becomes Settled, we take the sum of all settled payments of this payment method, get the specified percentage of it and add it to the current balance.</p>
                                    </div>
                                    <div class="form-group">
                                        <label class="form-label">Threshold</label>
                                        <div class="input-group input-group-sm">
                                            <input type="number" @bind="method.Value.Threshold" min="@supportedMethod?.FiatMinimumAmount" class="form-control form-control-sm"/>
                                            <span class="input-group-text">@(method.Value.FiatThreshold ? "EUR" : "BTC")</span>
                                        </div>
                                        <p class="text-muted  my-2">Once the threshold is reached, we create a payout sending the balance to Bringin to be converted.</p>

                                    </div>
                                    @if (supportedMethod?.FiatMinimum is not true)
                                    {
                                        <div class="form-group">
                                            <div class="d-flex align-items-center">
                                                <input type="checkbox" class="btcpay-toggle me-2" @bind="method.Value.FiatThreshold"/>
                                                <label class="form-label mb-0 me-1">Threshold in EUR</label>
                                            </div>
                                        </div>
                                    }

                                </div>
                            </div>
                        }
                    </div>
                </div>
            }
        }
    }
</div>
@if (LastTxs is not null)
{
    <div class="widget store-numbers">
        <header>
            <h4 class="text-muted">Bringin Transactions</h4>
        </header>
        @if (LastTxs.Transactions.Any())
        {
            <div class="table-responsive my-0 " style="    max-height: 400px;">
                <table class="table table-hover mt-3 mb-0">
                    <thead>
                    <tr>
                        <th>Date</th>
                        <th>Type</th>
                        <th>Status</th>
                        <th class="text-end">Amount</th>
                    </tr>
                    </thead>
                    <tbody>
                    @foreach (var tx in LastTxs.Transactions.OrderByDescending(transaction => transaction.CreatedAt))
                    {
                        <tr>
                            <td>@tx.CreatedAt.ToTimeAgo()</td>
                            <td>@tx.SubType.ToHumanReadable()</td>
                            <td>@tx.Status.ToHumanReadable()</td>
                            <td class="amount-col">
                                <span data-sensitive>
                                    @(tx.SourceCurrency == "BTC" ? Money.Satoshis(tx.SourceAmount).ToDecimal(MoneyUnit.BTC) : tx.SourceAmount) @tx.SourceCurrency
                                    -> @tx.DestinationAmount @tx.DestinationCurrency
                                </span>
                            </td>
                        </tr>
                    }
                    </tbody>s
                </table>
            </div>
        }
        else
        {
            <p class="text-secondary mt-3 mb-0">
                There are no recent transactions.
            </p>
        }
    </div>
}