# Path: billing/actions/accounts.py
# def assign_funds_to_account_pending_invoices(account_id: str) -> Sequence[str]:
#     """
#     Tries to pay pending account invoices (starting from the oldest) with available funds.
#     :param account_id: the account on which to perform the operation
#     :return: The ids of the invoices that were paid (possibly empty list).
#     """
#     logger.info('assign-funds-to-pending-invoices', account_id=str(account_id))
# 
#     paid_invoice_ids = []
#     for invoice in Invoice.objects.filter(status=Invoice.PENDING, account_id=account_id).order_by('due_date'):
#         invoice_was_paid = assign_funds_to_invoice(invoice.pk)
#         if invoice_was_paid:
#             paid_invoice_ids.append(invoice.id)
#         else:
#             break  # Bail even though there may be funds in another currency to pay more recent invoices.
#     logger.info('assign-funds-to-pending-invoices.end', account_id=str(account_id),
#                 paid_invoice_count=len(paid_invoice_ids))
#     return paid_invoice_ids
# 
# Path: billing/models.py
# class Account(Model):
#     OPEN = 'OPEN'
#     CLOSED = 'CLOSED'
#     STATUS_CHOICES = (
#         (OPEN, _('Open')),
#         (CLOSED, _('Closed')),
#     )
#     id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
#     created = models.DateTimeField(auto_now_add=True, db_index=True)
#     modified = models.DateTimeField(auto_now=True)
#     owner = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='billing_account', on_delete=PROTECT)
#     currency = CurrencyField(db_index=True)
#     status = FSMField(max_length=20, choices=STATUS_CHOICES, default=OPEN, db_index=True)
#     # An account is marked as delinquent when it is registered then when the user
#     # registers a valid credit card it will be marked as compliant
#     delinquent = models.BooleanField(default=True, db_index=True)
# 
#     objects = AccountQuerySet.as_manager()
# 
#     def __str__(self):
#         return str(self.owner)
# 
#     @transition(field=status, source=OPEN, target=CLOSED)
#     def close(self):
#         pass
# 
#     @transition(field=status, source=CLOSED, target=OPEN)
#     def reopen(self):
#         pass
# 
#     def balance(self, as_of: date = None):
#         charges = Charge.objects.filter(account=self)
#         transactions = Transaction.successful.filter(account=self)
#         if as_of is not None:
#             charges = charges.filter(created__lte=as_of)
#             transactions = transactions.filter(created__lte=as_of)
#         return total_amount(transactions) - total_amount(charges)
# 
#     def is_solvent(
#         self,
#         currency_threshold_price_map: Dict[str, Decimal],
#         account_valid_cc_map: Dict[UUID, bool] = None,
#         account_balance_map: DefaultDict[UUID, DefaultDict[str, Decimal]] = None
#     ):
#         """
#         Given a map of currency thresholds determines if the account is solvent
# 
#         An account is solvent when:
#             * Has a valid and active credit card to pay OR
#             * Has enough balance to pay
# 
#         In currency_threshold_price_map param you have to specify the amount threshold
#         for each currency. If the account has enough of one of the currencies then
#         is solvent. Ex:
#         currency_threshold_price_map {
#             'CHF': Decimal(10.83),
#             'EUR': Decimal(10.),
#             'NOK': Decimal(103.97)
#         }
# 
#         Note: account_valid_cc_map and account_balance_map can be passed from outside
#               in order to improve the efficiency when we require to know if several
#               accounts are solvent
#         """
#         if not account_valid_cc_map:
#             from .actions.accounts import get_account_valid_credit_card_map
#             account_valid_cc_map = get_account_valid_credit_card_map(
#                 Account.objects.filter(id=self.id)
#             )
# 
#         if not account_balance_map:
#             from .actions.accounts import get_account_balance_map
#             accounts = Account.objects.filter(id=self.id)
#             account_balance_map = get_account_balance_map(accounts)
# 
#         return (
#             account_valid_cc_map[self.id] or
#             self.has_enough_balance(account_balance_map, currency_threshold_price_map)
#         )
# 
#     def has_enough_balance(
#         self,
#         account_balance_map: DefaultDict[UUID, DefaultDict[str, Decimal]],
#         currency_threshold_price_map: Dict[str, Decimal]
#     ) -> bool:
#         for currency, balance in account_balance_map[self.id].items():
#             if balance >= currency_threshold_price_map[currency]:
#                 return True
#         return False
# 
#     def has_usable_credit_cards(self) -> bool:
#         credit_cards = CreditCard.objects.filter(
#             account=self,
#             status=CreditCard.ACTIVE
#         ).valid()
#         return credit_cards.exists()


import logging
import progressbar
import structlog
from collections import defaultdict
from django.core.management.base import BaseCommand
from ...actions.accounts import assign_funds_to_account_pending_invoices
from ...models import Account




def set_debug(logger_name):
    logger = logging.getLogger(logger_name)
    logger.setLevel(logging.DEBUG)
    logger.addHandler(logging.StreamHandler())


logger = structlog.get_logger()


class Command(BaseCommand):

# Path: billing/actions/accounts.py
# def assign_funds_to_account_pending_invoices(account_id: str) -> Sequence[str]:
#     """
#     Tries to pay pending account invoices (starting from the oldest) with available funds.
#     :param account_id: the account on which to perform the operation
#     :return: The ids of the invoices that were paid (possibly empty list).
#     """
#     logger.info('assign-funds-to-pending-invoices', account_id=str(account_id))
# 
#     paid_invoice_ids = []
#     for invoice in Invoice.objects.filter(status=Invoice.PENDING, account_id=account_id).order_by('due_date'):
#         invoice_was_paid = assign_funds_to_invoice(invoice.pk)
#         if invoice_was_paid:
#             paid_invoice_ids.append(invoice.id)
#         else:
#             break  # Bail even though there may be funds in another currency to pay more recent invoices.
#     logger.info('assign-funds-to-pending-invoices.end', account_id=str(account_id),
#                 paid_invoice_count=len(paid_invoice_ids))
#     return paid_invoice_ids
# 
# Path: billing/models.py
# class Account(Model):
#     OPEN = 'OPEN'
#     CLOSED = 'CLOSED'
#     STATUS_CHOICES = (
#         (OPEN, _('Open')),
#         (CLOSED, _('Closed')),
#     )
#     id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
#     created = models.DateTimeField(auto_now_add=True, db_index=True)
#     modified = models.DateTimeField(auto_now=True)
#     owner = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='billing_account', on_delete=PROTECT)
#     currency = CurrencyField(db_index=True)
#     status = FSMField(max_length=20, choices=STATUS_CHOICES, default=OPEN, db_index=True)
#     # An account is marked as delinquent when it is registered then when the user
#     # registers a valid credit card it will be marked as compliant
#     delinquent = models.BooleanField(default=True, db_index=True)
# 
#     objects = AccountQuerySet.as_manager()
# 
#     def __str__(self):
#         return str(self.owner)
# 
#     @transition(field=status, source=OPEN, target=CLOSED)
#     def close(self):
#         pass
# 
#     @transition(field=status, source=CLOSED, target=OPEN)
#     def reopen(self):
#         pass
# 
#     def balance(self, as_of: date = None):
#         charges = Charge.objects.filter(account=self)
#         transactions = Transaction.successful.filter(account=self)
#         if as_of is not None:
#             charges = charges.filter(created__lte=as_of)
#             transactions = transactions.filter(created__lte=as_of)
#         return total_amount(transactions) - total_amount(charges)
# 
#     def is_solvent(
#         self,
#         currency_threshold_price_map: Dict[str, Decimal],
#         account_valid_cc_map: Dict[UUID, bool] = None,
#         account_balance_map: DefaultDict[UUID, DefaultDict[str, Decimal]] = None
#     ):
#         """
#         Given a map of currency thresholds determines if the account is solvent
# 
#         An account is solvent when:
#             * Has a valid and active credit card to pay OR
#             * Has enough balance to pay
# 
#         In currency_threshold_price_map param you have to specify the amount threshold
#         for each currency. If the account has enough of one of the currencies then
#         is solvent. Ex:
#         currency_threshold_price_map {
#             'CHF': Decimal(10.83),
#             'EUR': Decimal(10.),
#             'NOK': Decimal(103.97)
#         }
# 
#         Note: account_valid_cc_map and account_balance_map can be passed from outside
#               in order to improve the efficiency when we require to know if several
#               accounts are solvent
#         """
#         if not account_valid_cc_map:
#             from .actions.accounts import get_account_valid_credit_card_map
#             account_valid_cc_map = get_account_valid_credit_card_map(
#                 Account.objects.filter(id=self.id)
#             )
# 
#         if not account_balance_map:
#             from .actions.accounts import get_account_balance_map
#             accounts = Account.objects.filter(id=self.id)
#             account_balance_map = get_account_balance_map(accounts)
# 
#         return (
#             account_valid_cc_map[self.id] or
#             self.has_enough_balance(account_balance_map, currency_threshold_price_map)
#         )
# 
#     def has_enough_balance(
#         self,
#         account_balance_map: DefaultDict[UUID, DefaultDict[str, Decimal]],
#         currency_threshold_price_map: Dict[str, Decimal]
#     ) -> bool:
#         for currency, balance in account_balance_map[self.id].items():
#             if balance >= currency_threshold_price_map[currency]:
#                 return True
#         return False
# 
#     def has_usable_credit_cards(self) -> bool:
#         credit_cards = CreditCard.objects.filter(
#             account=self,
#             status=CreditCard.ACTIVE
#         ).valid()
#         return credit_cards.exists()


# Path: billing/management/commands/match_funds.py
import logging
import progressbar
import structlog
from collections import defaultdict
from django.core.management.base import BaseCommand
from ...actions.accounts import assign_funds_to_account_pending_invoices
from ...models import Account





def set_debug(logger_name):
    logger = logging.getLogger(logger_name)
    logger.setLevel(logging.DEBUG)
    logger.addHandler(logging.StreamHandler())


logger = structlog.get_logger()


class Command(BaseCommand):

    help = """For all accounts with pending invoices: tries to match unassigned funds to invoices.