"""
Account management for the Tradier API.

This module provides the Account class for retrieving account information,
balances, and positions.
"""

from __future__ import annotations

from datetime import date, datetime
from decimal import Decimal
from enum import Enum
from typing import TYPE_CHECKING

from loguru import logger
from pydantic import Field

from optrabot.broker.tradier.utils import TradierData

if TYPE_CHECKING:
    from optrabot.broker.tradier.session import Session


# ========== Balance Models ==========


class MarginBalance(TradierData):
    """Margin-specific balance information."""

    fed_call: Decimal = Decimal('0')
    maintenance_call: Decimal = Decimal('0')
    option_buying_power: Decimal = Decimal('0')
    stock_buying_power: Decimal = Decimal('0')
    stock_short_value: Decimal = Decimal('0')
    sweep: Decimal = Decimal('0')


class CashBalance(TradierData):
    """Cash-specific balance information."""

    cash_available: Decimal = Decimal('0')
    sweep: Decimal = Decimal('0')
    unsettled_funds: Decimal = Decimal('0')


class PDTBalance(TradierData):
    """Pattern Day Trader balance information."""

    fed_call: Decimal = Decimal('0')
    maintenance_call: Decimal = Decimal('0')
    option_buying_power: Decimal = Decimal('0')
    stock_buying_power: Decimal = Decimal('0')
    stock_short_value: Decimal = Decimal('0')


class AccountBalance(TradierData):
    """
    Account balance information.

    Contains the current balance, equity, and buying power for an account.
    Different account types (margin, cash, pdt) have different sub-fields.

    Example::

        balance = account.get_balance(session)
        print(f"Total Equity: ${balance.total_equity}")
        print(f"Cash: ${balance.total_cash}")
        if balance.margin:
            print(f"Buying Power: ${balance.margin.stock_buying_power}")
    """

    # Core balance fields
    account_number: str
    account_type: str
    total_equity: Decimal = Decimal('0')
    total_cash: Decimal = Decimal('0')
    equity: Decimal = Decimal('0')
    market_value: Decimal = Decimal('0')

    # Position values
    long_market_value: Decimal = Decimal('0')
    short_market_value: Decimal = Decimal('0')
    stock_long_value: Decimal = Decimal('0')
    option_long_value: Decimal = Decimal('0')
    option_short_value: Decimal = Decimal('0')

    # P&L
    open_pl: Decimal = Decimal('0')
    close_pl: Decimal = Decimal('0')

    # Margin requirements
    current_requirement: Decimal = Decimal('0')
    option_requirement: Decimal = Decimal('0')

    # Cash status
    pending_cash: Decimal = Decimal('0')
    uncleared_funds: Decimal = Decimal('0')
    pending_orders_count: int = 0

    # Type-specific balances (only one will be populated based on account type)
    margin: MarginBalance | None = None
    cash: CashBalance | None = None
    pdt: PDTBalance | None = None

    def __repr__(self) -> str:
        return (
            f'AccountBalance(account={self.account_number!r}, '
            f'equity=${self.total_equity}, cash=${self.total_cash})'
        )


# ========== Account History Models ==========


class AccountHistoryTrade(TradierData):
    """Trade details within an account history event.

    Contains the commission and fee information for a trade execution.

    The Tradier API returns trade events with commission and fee data
    per individual leg/fill of a trade.
    """

    commission: Decimal = Decimal('0')
    fee: Decimal = Decimal('0')
    quantity: Decimal | None = None
    price: Decimal | None = None
    symbol: str | None = None
    trade_type: str | None = None
    description: str | None = None
    option_type: str | None = None

    def __repr__(self) -> str:
        return (
            f'AccountHistoryTrade(symbol={self.symbol!r}, '
            f'commission={self.commission}, fee={self.fee})'
        )


class AccountHistoryEvent(TradierData):
    """An event from the account history (transaction history).

    Represents a single entry in the account's transaction history,
    typically a trade execution with associated commission and fee data.

    Example::

        events = account.get_history(session, start=date.today(), type='trade')
        for event in events:
            if event.trade:
                print(f"Commission: ${event.trade.commission}")
                print(f"Fee: ${event.trade.fee}")
    """

    amount: Decimal | None = None
    date: datetime | None = None
    type: str | None = None
    trade: AccountHistoryTrade | None = None

    def __repr__(self) -> str:
        return (
            f'AccountHistoryEvent(type={self.type!r}, '
            f'date={self.date}, amount={self.amount})'
        )


class AccountClassification(str, Enum):
    """Account classification types."""

    INDIVIDUAL = 'individual'
    INDIVIDUAL_CASH = 'individual_cash'
    INDIVIDUAL_MARGIN = 'individual_margin'
    ENTITY = 'entity'
    ENTITY_CASH = 'entity_cash'
    ENTITY_MARGIN = 'entity_margin'
    JOINT = 'joint'
    JOINT_CASH_SURVIVOR = 'joint_cash_survivor'
    JOINT_MARGIN_SURVIVOR = 'joint_margin_survivor'
    JOINT_MARGIN_TENANT = 'joint_margin_tenant'
    CORPORATE = 'corporate'
    TRADITIONAL_IRA = 'traditional_ira'
    ROTH_IRA = 'roth_ira'
    ROLLOVER_IRA = 'rollover_ira'
    SEP_IRA = 'sep_ira'
    TRADITIONAL_SEP_IRA = 'traditional_sep_ira'
    CUSTODIAL_CASH = 'custodial_cash'


class AccountStatus(str, Enum):
    """Account status types."""

    ACTIVE = 'active'
    CLOSED = 'closed'


class AccountType(str, Enum):
    """Account types."""

    CASH = 'cash'
    MARGIN = 'margin'


class Account(TradierData):
    """
    Represents a Tradier brokerage account.

    Use the class methods to retrieve accounts from the API:

    Example::

        from optrabot.broker.tradier import Session, Account

        session = Session("your-access-token")

        # Get all accounts
        accounts = Account.get_accounts(session)

        # Get a specific account
        account = Account.get(session, "VA000001")
    """

    account_number: str
    classification: str
    date_created: datetime
    day_trader: bool
    option_level: int
    status: AccountStatus
    type: AccountType = Field(alias='type')
    last_update_date: datetime

    def __repr__(self) -> str:
        return (
            f'Account(account_number={self.account_number!r}, '
            f'type={self.type!r}, status={self.status!r})'
        )

    # ========== Class Methods for Retrieving Accounts ==========

    @classmethod
    def get_accounts(cls, session: Session) -> list[Account]:
        """
        Get all accounts associated with the current user.

        :param session: The session to use for the request.
        :returns: A list of Account objects.

        Example::

            accounts = Account.get_accounts(session)
            for account in accounts:
                print(f"{account.account_number}: {account.type}")
        """
        data = session._get('/user/profile')
        return cls._parse_accounts(data)

    @classmethod
    async def a_get_accounts(cls, session: Session) -> list[Account]:
        """
        Get all accounts associated with the current user (async).

        :param session: The session to use for the request.
        :returns: A list of Account objects.

        Example::

            accounts = await Account.a_get_accounts(session)
            for account in accounts:
                print(f"{account.account_number}: {account.type}")
        """
        data = await session._a_get('/user/profile')
        return cls._parse_accounts(data)

    @classmethod
    def get(cls, session: Session, account_number: str) -> Account:
        """
        Get a specific account by account number.

        :param session: The session to use for the request.
        :param account_number: The account number to retrieve.
        :returns: The Account object.
        :raises TradierError: If the account is not found.

        Example::

            account = Account.get(session, "VA000001")
            print(f"Option Level: {account.option_level}")
        """
        accounts = cls.get_accounts(session)
        return cls._find_account(accounts, account_number)

    @classmethod
    async def a_get(cls, session: Session, account_number: str) -> Account:
        """
        Get a specific account by account number (async).

        :param session: The session to use for the request.
        :param account_number: The account number to retrieve.
        :returns: The Account object.
        :raises TradierError: If the account is not found.

        Example::

            account = await Account.a_get(session, "VA000001")
            print(f"Option Level: {account.option_level}")
        """
        accounts = await cls.a_get_accounts(session)
        return cls._find_account(accounts, account_number)

    # ========== Instance Methods for Account Data ==========

    def get_balance(self, session: Session) -> AccountBalance:
        """
        Get the current balance for this account.

        :param session: The session to use for the request.
        :returns: An AccountBalance object with balance details.

        Example::

            balance = account.get_balance(session)
            print(f"Total Equity: ${balance.total_equity}")
            print(f"Total Cash: ${balance.total_cash}")
            if balance.margin:
                print(f"Stock Buying Power: ${balance.margin.stock_buying_power}")
        """
        data = session._get(f'/accounts/{self.account_number}/balances')
        return self._parse_balance(data)

    async def a_get_balance(self, session: Session) -> AccountBalance:
        """
        Get the current balance for this account (async).

        :param session: The session to use for the request.
        :returns: An AccountBalance object with balance details.

        Example::

            balance = await account.a_get_balance(session)
            print(f"Total Equity: ${balance.total_equity}")
        """
        data = await session._a_get(f'/accounts/{self.account_number}/balances')
        return self._parse_balance(data)

    def _parse_balance(self, data: dict) -> AccountBalance:
        """Parse the balance response and return an AccountBalance object."""
        balances_data = data.get('balances', {})
        logger.debug(
            'Retrieved balance for account {}: equity=${}',
            self.account_number,
            balances_data.get('total_equity', 0),
        )
        return AccountBalance(**balances_data)

    # ========== Account History Methods ==========

    def get_history(
        self,
        session: Session,
        start: date | None = None,
        end: date | None = None,
        type: str | None = None,
        limit: int = 100,
    ) -> list[AccountHistoryEvent]:
        """
        Get account transaction history.

        Retrieves trade history including commission and fee data
        for executed trades.

        :param session: The session to use for the request.
        :param start: Start date for the history query.
        :param end: End date for the history query.
        :param type: Filter by event type (e.g., 'trade', 'option', 'ach', 'wire').
                     Use None to retrieve all event types.
        :param limit: Maximum number of events to return.
        :returns: A list of AccountHistoryEvent objects.

        Example::

            from datetime import date
            events = account.get_history(session, start=date.today(), type='trade')
            for event in events:
                print(f"Trade: {event.trade.symbol} Fee: ${event.trade.fee}")
        """
        params = self._build_history_params(start, end, type, limit)
        data = session._get(f'/accounts/{self.account_number}/history', params=params)
        return self._parse_history(data)

    async def a_get_history(
        self,
        session: Session,
        start: date | None = None,
        end: date | None = None,
        type: str | None = None,
        limit: int = 100,
    ) -> list[AccountHistoryEvent]:
        """
        Get account transaction history (async).

        :param session: The session to use for the request.
        :param start: Start date for the history query.
        :param end: End date for the history query.
        :param type: Filter by event type (e.g., 'trade', 'option', 'ach', 'wire').
                     Use None to retrieve all event types.
        :param limit: Maximum number of events to return.
        :returns: A list of AccountHistoryEvent objects.

        Example::

            events = await account.a_get_history(session, start=date.today())
        """
        params = self._build_history_params(start, end, type, limit)
        data = await session._a_get(f'/accounts/{self.account_number}/history', params=params)
        return self._parse_history(data)

    @staticmethod
    def _build_history_params(
        start: date | None,
        end: date | None,
        type: str | None,
        limit: int,
    ) -> dict:
        """Build query parameters for the history endpoint."""
        params: dict = {'limit': limit}
        if start:
            params['start'] = start.isoformat()
        if end:
            params['end'] = end.isoformat()
        if type:
            params['type'] = type
        return params

    @staticmethod
    def _parse_history(data: dict) -> list[AccountHistoryEvent]:
        """Parse the account history response.

        Handles the Tradier API pattern where a single result is returned
        as a dict rather than a list.
        """
        history_data = data.get('history')
        if not history_data or history_data == 'null':
            return []

        event_list = history_data.get('event', [])

        # Handle single event (API returns dict) vs multiple (returns list)
        if isinstance(event_list, dict):
            event_list = [event_list]

        events = []
        for event_data in event_list:
            # Parse nested trade object if present
            trade_data = event_data.get('trade')
            if trade_data and isinstance(trade_data, dict):
                event_data = {**event_data, 'trade': AccountHistoryTrade(**trade_data)}
            event = AccountHistoryEvent(**event_data)
            # OTB-390: Compute implicit exchange/regulatory fees from amount
            Account._compute_implicit_fee(event)
            events.append(event)

        logger.trace('Retrieved {} history event(s) for account', len(events))
        return events

    @staticmethod
    def _compute_implicit_fee(event: AccountHistoryEvent) -> None:
        """Compute implicit exchange/regulatory fees from the event amount.

        The Tradier Account History API does **not** return a ``fee`` field.
        Exchange and regulatory fees (ORF, SEC, TAF, etc.) are silently
        embedded in the ``amount`` field.  This method reverse-engineers the
        fee with::

            raw_amount   = -(price × quantity × multiplier)
            fee          = (raw_amount - event.amount) - commission

        The computed fee is stored directly in ``event.trade.fee``.
        """
        if not event.trade or event.amount is None:
            return
        trade = event.trade
        if trade.price is None or trade.quantity is None:
            return

        multiplier = Decimal('100') if trade.trade_type == 'option' else Decimal('1')
        raw_amount = -(trade.price * trade.quantity * multiplier)
        commission = abs(trade.commission) if trade.commission else Decimal('0')
        implicit_fee = raw_amount - event.amount - commission

        if implicit_fee > 0:
            trade.fee = implicit_fee

    # ========== Private Helper Methods ==========

    @classmethod
    def _parse_accounts(cls, data: dict) -> list[Account]:
        """Parse the profile response and return a list of accounts."""
        profile = data.get('profile', {})
        accounts_data = profile.get('account', [])

        # Handle single account (API returns dict) vs multiple (returns list)
        if isinstance(accounts_data, dict):
            accounts_data = [accounts_data]

        accounts = [cls(**account) for account in accounts_data]
        logger.trace('Retrieved {} account(s)', len(accounts))
        return accounts

    @classmethod
    def _find_account(cls, accounts: list[Account], account_number: str) -> Account:
        """Find an account by account number."""
        from optrabot.broker.tradier.exceptions import TradierError

        for account in accounts:
            if account.account_number == account_number:
                return account

        raise TradierError(f'Account {account_number} not found')
