from celery import shared_task


@shared_task(name="do_accounting", time_limit=60)
def do_accounting():
    """
    Does all accounting from the beginning of time
    """

    from datetime import timedelta

    from decouple import config
    from django.db.models import Sum
    from django.utils import timezone

    from api.models import LNPayment, MarketTick, OnchainPayment, Order, Robot
    from control.models import AccountingDay

    all_payments = LNPayment.objects.all()
    all_ticks = MarketTick.objects.all()
    today = timezone.now().date()

    try:
        last_accounted_day = AccountingDay.objects.latest("day").day.date()
        accounted_yesterday = AccountingDay.objects.latest("day")
    except Exception:
        last_accounted_day = None
        accounted_yesterday = None

    if last_accounted_day == today:
        return {"message": "no days to account for"}
    elif last_accounted_day is not None:
        initial_day = last_accounted_day + timedelta(days=1)
    elif last_accounted_day is None:
        initial_day = all_payments.earliest("created_at").created_at.date()

    day = initial_day
    result = {}
    while day <= today:
        day_payments = all_payments.filter(
            created_at__gte=day, created_at__lte=day + timedelta(days=1)
        )
        day_onchain_payments = OnchainPayment.objects.filter(
            created_at__gte=day, created_at__lte=day + timedelta(days=1)
        )
        day_ticks = all_ticks.filter(
            timestamp__gte=day, timestamp__lte=day + timedelta(days=1)
        )

        # Coarse accounting based on LNpayment and OnchainPayment objects
        contracted = day_ticks.aggregate(Sum("volume"))["volume__sum"]
        num_contracts = day_ticks.count()
        inflow = day_payments.filter(
            type=LNPayment.Types.HOLD, status=LNPayment.Status.SETLED
        ).aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
        onchain_outflow = day_onchain_payments.filter(
            status__in=[OnchainPayment.Status.MEMPO, OnchainPayment.Status.CONFI]
        ).aggregate(Sum("sent_satoshis"))["sent_satoshis__sum"]
        onchain_outflow = 0 if onchain_outflow is None else int(onchain_outflow)
        offchain_outflow = day_payments.filter(
            type=LNPayment.Types.NORM, status=LNPayment.Status.SUCCED
        ).aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
        offchain_outflow = 0 if offchain_outflow is None else int(offchain_outflow)
        routing_fees = day_payments.filter(
            type=LNPayment.Types.NORM, status=LNPayment.Status.SUCCED
        ).aggregate(Sum("fee"))["fee__sum"]
        mining_fees = day_onchain_payments.filter(
            status__in=[OnchainPayment.Status.MEMPO, OnchainPayment.Status.CONFI]
        ).aggregate(Sum("mining_fee_sats"))["mining_fee_sats__sum"]
        rewards_claimed = day_payments.filter(
            type=LNPayment.Types.NORM,
            concept=LNPayment.Concepts.WITHREWA,
            status=LNPayment.Status.SUCCED,
        ).aggregate(Sum("num_satoshis"))["num_satoshis__sum"]

        contracted = 0 if contracted is None else contracted
        inflow = 0 if inflow is None else inflow
        outflow = offchain_outflow + onchain_outflow
        routing_fees = 0 if routing_fees is None else routing_fees
        rewards_claimed = 0 if rewards_claimed is None else rewards_claimed
        mining_fees = 0 if mining_fees is None else mining_fees

        accounted_day = AccountingDay.objects.create(
            day=day,
            contracted=contracted,
            num_contracts=num_contracts,
            inflow=inflow,
            outflow=outflow,
            routing_fees=routing_fees,
            mining_fees=mining_fees,
            cashflow=inflow - outflow - routing_fees,
            rewards_claimed=rewards_claimed,
        )

        # Fine Net Daily accounting based on orders
        # Only account for orders where everything worked out right
        payouts = day_payments.filter(
            type=LNPayment.Types.NORM,
            concept=LNPayment.Concepts.PAYBUYER,
            status=LNPayment.Status.SUCCED,
        )
        escrows_settled = 0
        payouts_paid = 0
        costs = 0
        for payout in payouts:
            escrows_settled += int(payout.order_paid_LN.trade_escrow.num_satoshis)
            payouts_paid += int(payout.num_satoshis)
            costs += int(payout.fee)

        # Same for orders that use onchain payments.
        payouts_tx = day_onchain_payments.filter(
            status__in=[OnchainPayment.Status.MEMPO, OnchainPayment.Status.CONFI]
        )
        for payout_tx in payouts_tx:
            escrows_settled += int(payout_tx.order_paid_TX.trade_escrow.num_satoshis)
            payouts_paid += int(payout_tx.sent_satoshis)
            costs += int(payout_tx.mining_fee_sats)

        # account for those orders where bonds were lost
        # + Settled bonds / bond_split
        bonds_settled = day_payments.filter(
            type=LNPayment.Types.HOLD,
            concept__in=[LNPayment.Concepts.TAKEBOND, LNPayment.Concepts.MAKEBOND],
            status=LNPayment.Status.SETLED,
        )

        if len(bonds_settled) > 0:
            collected_slashed_bonds = (
                bonds_settled.aggregate(Sum("num_satoshis"))["num_satoshis__sum"]
            ) * float(config("SLASHED_BOND_REWARD_SPLIT"))
        else:
            collected_slashed_bonds = 0

        accounted_day.net_settled = escrows_settled + collected_slashed_bonds
        accounted_day.net_paid = payouts_paid + costs
        accounted_day.net_balance = float(accounted_day.net_settled) - float(
            accounted_day.net_paid
        )

        # Differential accounting based on change of outstanding states and disputes unreslved
        if day == today:
            pending_disputes = Order.objects.filter(
                status__in=[Order.Status.DIS, Order.Status.WFR]
            )
            if len(pending_disputes) > 0:
                outstanding_pending_disputes = 0
                for order in pending_disputes:
                    outstanding_pending_disputes += order.payout.num_satoshis
            else:
                outstanding_pending_disputes = 0

            accounted_day.outstanding_earned_rewards = Robot.objects.all().aggregate(
                Sum("earned_rewards")
            )["earned_rewards__sum"]
            accounted_day.outstanding_pending_disputes = outstanding_pending_disputes
            accounted_day.lifetime_rewards_claimed = Robot.objects.all().aggregate(
                Sum("claimed_rewards")
            )["claimed_rewards__sum"]
            if accounted_yesterday is not None:
                accounted_day.earned_rewards = (
                    accounted_day.outstanding_earned_rewards
                    - accounted_yesterday.outstanding_earned_rewards
                )
                accounted_day.disputes = (
                    outstanding_pending_disputes
                    - accounted_yesterday.outstanding_earned_rewards
                )

        # Close the loop
        accounted_day.save()
        accounted_yesterday = accounted_day
        result[str(day)] = {
            "contracted": contracted,
            "inflow": inflow,
            "outflow": outflow,
        }
        day = day + timedelta(days=1)

    return result


@shared_task(name="compute_node_balance", ignore_result=True, time_limit=10)
def compute_node_balance():
    """
    Queries LND for channel and wallet balance
    """

    from control.models import BalanceLog

    BalanceLog.objects.create()

    return