from datetime import datetime
from sqlalchemy import select
from sqlalchemy.orm import sessionmaker

from bagels.config import CONFIG
from bagels.models.account import Account
from bagels.models.record import Record
from bagels.models.split import Split
from bagels.models.database.app import db_engine

Session = sessionmaker(bind=db_engine)


def get_account_balance(accountId, session=None):
    """Returns the net balance of an account.

    Rules:
    - Consider all record "account" and split "account"
    - Records with isTransfer should consider both "account" and "transferToAccount"
    - Records and splits should be considered separately, unlike net figures which consider records and splits together.

    Args:
        accountId (int): The ID of the account to get the balance
        session (Session, optional): SQLAlchemy session to use. If None, creates a new session.
    """
    if session is None:
        session = Session()
        should_close = True
    else:
        should_close = False

    try:
        # Initialize balance
        balance = (
            session.query(Account)
            .filter(Account.id == accountId)
            .first()
            .beginningBalance
        )

        # Get all records for this account
        records = session.query(Record).filter(Record.accountId == accountId).all()

        # Calculate balance from records
        for record in records:
            if record.isTransfer:
                # For transfers, subtract full amount (transfers out)
                balance -= record.amount
            elif record.isIncome:
                # For income records, add full amount
                balance += record.amount
            else:
                # For expense records, subtract full amount
                balance -= record.amount

        # Get all records where this account is the transfer destination
        transfer_to_records = (
            session.query(Record)
            .filter(Record.transferToAccountId == accountId, Record.isTransfer == True)
            .all()
        )

        # Add transfers into this account
        for record in transfer_to_records:
            balance += record.amount

        # Get all splits where this account is specified
        splits = session.query(Split).filter(Split.accountId == accountId).all()

        # Add paid splits (they represent money coming into this account)
        for split in splits:
            if split.isPaid:
                if split.record.isIncome:
                    balance -= split.amount
                else:
                    balance += split.amount

        return round(balance, CONFIG.defaults.round_decimals)
    finally:
        if should_close:
            session.close()


def create_account(data):
    session = Session()
    try:
        new_account = Account(**data)
        session.add(new_account)
        session.commit()
        session.refresh(new_account)
        session.expunge(new_account)
        return new_account
    finally:
        session.close()


def _get_base_accounts_query(get_hidden=False):
    stmt = select(Account).filter(Account.deletedAt.is_(None))
    if not get_hidden:
        stmt = stmt.filter(Account.hidden.is_(False))
    else:
        stmt = stmt.order_by(Account.hidden)
    return stmt


def get_all_accounts(get_hidden=False):
    session = Session()
    try:
        stmt = _get_base_accounts_query(get_hidden)
        return session.scalars(stmt).all()
    finally:
        session.close()


def get_accounts_count(get_hidden=False):
    session = Session()
    try:
        stmt = _get_base_accounts_query(get_hidden)
        return len(session.scalars(stmt).all())
    finally:
        session.close()


def get_all_accounts_with_balance(get_hidden=False):
    session = Session()
    try:
        stmt = _get_base_accounts_query(get_hidden)
        accounts = session.scalars(stmt).all()
        for account in accounts:
            account.balance = get_account_balance(account.id, session)
        return accounts
    finally:
        session.close()


def get_account_balance_by_id(account_id):
    session = Session()
    try:
        return get_account_balance(account_id, session)
    finally:
        session.close()


def get_account_by_id(account_id):
    session = Session()
    try:
        return session.get(Account, account_id)
    finally:
        session.close()


def update_account(account_id, data):
    session = Session()
    try:
        account = session.get(Account, account_id)
        if account:
            for key, value in data.items():
                setattr(account, key, value)
            session.commit()
            session.refresh(account)
            session.expunge(account)
        return account
    finally:
        session.close()


def delete_account(account_id):
    session = Session()
    try:
        account = session.get(Account, account_id)
        if account:
            account.deletedAt = datetime.now()
            session.commit()
            return True
        return False
    finally:
        session.close()
