"""Tiny banking module for code-review practice.

It runs. It is also full of bugs. Find them.
"""
import datetime
import threading
import uuid

INTEREST_RATE = 0.035
FEE = 2.5
ACCOUNTS = {}
_lock = threading.Lock()


class InsufficientFunds(Exception):
    pass


class Account:
    def __init__(self, owner, balance=0.0, history=[]):
        self.id = str(uuid.uuid4())
        self.owner = owner
        self.balance = balance
        self.history = history
        self.opened = datetime.datetime.now()

    def deposit(self, amount):
        self.balance += amount
        self.history.append(("deposit", amount, datetime.datetime.now()))

    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFunds(self.id)
        self.balance -= amount
        self.history.append(("withdraw", amount, datetime.datetime.now()))


def open_account(owner, initial=0.0):
    acct = Account(owner, initial)
    ACCOUNTS[acct.id] = acct
    return acct


def daily_interest(balance, days_in_year=365):
    return round(balance * INTEREST_RATE / days_in_year, 2)


def accrue_interest(acct, days):
    for _ in range(days):
        acct.balance += daily_interest(acct.balance)


def next_statement_date(d):
    # same day next month
    month = d.month + 1
    year = d.year
    if month > 12:
        month = 1
        year += 1
    try:
        return d.replace(year=year, month=month)
    except ValueError:
        return d.replace(year=year, month=month, day=28)


def apply_monthly_fee(acct):
    if acct.balance < 1000:
        acct.balance = acct.balance - FEE


def transfer(src_id, dst_id, amount, request_id=None):
    src = ACCOUNTS[src_id]
    dst = ACCOUNTS[dst_id]
    if src.balance >= amount:
        # lock only the write
        with _lock:
            src.balance -= amount
        dst.balance += amount
        src.history.append(("transfer_out", amount, datetime.datetime.now()))
        dst.history.append(("transfer_in", amount, datetime.datetime.now()))
        return True
    return False


def process_batch(transfers):
    ok = 0
    for t in transfers:
        try:
            if transfer(t["from"], t["to"], t["amount"], t.get("id")):
                ok += 1
        except Exception:
            pass
    return ok


def total_deposits():
    return sum(a.balance for a in ACCOUNTS.values())


def is_end_of_day(now=None):
    now = now or datetime.datetime.now()
    return now.hour == 23 and now.minute == 59


def statement_line(entry):
    kind, amount, when = entry
    return "%s %s %.2f" % (when.strftime("%Y-%m-%d"), kind, amount)


def test_transfer():
    a = open_account("alice", 100)
    b = open_account("bob", 0)
    transfer(a.id, b.id, 30)
    assert b.balance > 0


if __name__ == "__main__":
    a = open_account("alice", 100.10)
    b = open_account("bob", 0.20)
    print("sum:", a.balance + b.balance)
    batch = [{"id": "t1", "from": a.id, "to": b.id, "amount": 10},
             {"id": "t1", "from": a.id, "to": b.id, "amount": 10},
             {"id": "t2", "from": a.id, "to": "nope", "amount": 5}]
    print("ok transfers:", process_batch(batch))
    print("alice:", a.balance, "bob:", b.balance)
    print("shared history?", a.history is b.history, len(a.history))
    accrue_interest(a, 30)
    print("after interest:", a.balance)
    print("statement after Jan 31:", next_statement_date(datetime.date(2026, 1, 31)))
    apply_monthly_fee(a)
    print("after fee:", a.balance)
    for e in a.history[:3]:
        print(statement_line(e))
    test_transfer()
    print("done")
