"""
Trades API Endpoints

This module provides API endpoints for managing and viewing trades.
"""

import asyncio
import copy
from datetime import datetime, timedelta
from enum import Enum
from typing import List, Optional

import pytz
from fastapi import APIRouter, HTTPException, Query
from loguru import logger
from pydantic import BaseModel

from optrabot import config as optrabotcfg
from optrabot.broker.order import OptionRight, OrderAction
from optrabot.database import SessionLocal
from optrabot.managedtrade import ManagedTrade
from optrabot.models import Trade as TradeModel
from optrabot.models import Transaction as TransactionModel
from optrabot.signaldata import SignalData
from optrabot.tradehelper import TradeHelper, group_transactions
from optrabot.trademanager import TradeManager
from optrabot.tradestatus import TradeStatus
from optrabot.tradetemplate.processor.templateprocessor import \
    TemplateProcessor
from optrabot.tradetemplate.templatefactory import Template

router = APIRouter(prefix='/api')


def _sanitize_float(value: Optional[float]) -> Optional[float]:
    """
    Sanitize a float value for JSON serialization.
    Converts NaN and Infinity values to None since they are not JSON compliant.
    """
    import math
    if value is None:
        return None
    if math.isnan(value) or math.isinf(value):
        return None
    return value


class LegInfo(BaseModel):
    """Leg information for display"""
    strike: float
    right: str  # 'CALL' or 'PUT'
    action: str  # 'BUY' or 'SELL'
    expiration: Optional[str] = None


class TradeInfo(BaseModel):
    """Trade information for the frontend"""
    id: int
    template_name: str
    template_group: Optional[str]
    strategy: str
    account: str
    symbol: str
    status: str
    entry_price: Optional[float]
    current_price: Optional[float]
    current_pnl: Optional[float]
    amount: int
    entry_time: Optional[str]
    close_time: Optional[str] = None
    fees: Optional[float] = None
    commission: Optional[float] = None
    legs: List[LegInfo]
    expiration: Optional[str]


class CloseTradeRequest(BaseModel):
    """Request model for closing a trade"""
    trade_id: int
    trigger_flow: bool = True  # If False, no flow events will be triggered


class CloseTradeResponse(BaseModel):
    """Response model for close trade request"""
    success: bool
    message: str
    trade_id: int


class MarkAsClosedRequest(BaseModel):
    """Request model for marking a trade as closed without broker order (OTB-336)
    
    Used when the user has already manually closed the trade at the broker
    and just needs to update the trade status and create closing transactions.
    
    Note: No flow events are triggered when marking a trade as closed,
    since this is only a bookkeeping operation.
    """
    trade_id: int
    close_price: float  # Total closing price (premium) for the trade
    fees: float = 0.0  # Total fees for closing


class MarkAsClosedResponse(BaseModel):
    """Response model for mark as closed request"""
    success: bool
    message: str
    trade_id: int
    realized_pnl: Optional[float] = None


class TimeRange(str, Enum):
    """Time range filter options"""
    TODAY = 'today'
    YESTERDAY = 'yesterday'
    THIS_WEEK = 'this_week'
    ALL = 'all'
    CUSTOM = 'custom'


def _get_time_range_start(time_range: TimeRange) -> Optional[datetime]:
    """Get the start datetime for the given time range filter"""
    now = datetime.now(pytz.timezone('US/Eastern'))
    
    if time_range == TimeRange.TODAY:
        return now.replace(hour=0, minute=0, second=0, microsecond=0)
    elif time_range == TimeRange.YESTERDAY:
        yesterday = now - timedelta(days=1)
        return yesterday.replace(hour=0, minute=0, second=0, microsecond=0)
    elif time_range == TimeRange.THIS_WEEK:
        # Start of current week (Monday)
        days_since_monday = now.weekday()
        week_start = now - timedelta(days=days_since_monday)
        return week_start.replace(hour=0, minute=0, second=0, microsecond=0)
    else:
        return None  # ALL - no filter


def _db_trade_to_info(db_trade: TradeModel, templates: list | None = None) -> TradeInfo:
    """Convert a database Trade model to TradeInfo for API response
    
    Args:
        db_trade: The database Trade model
        templates: Optional list of templates to look up template_group
    """
    
    # Try to find template_group from templates list
    template_group = None
    template_name = db_trade.template_name or db_trade.strategy
    if templates and template_name:
        for template in templates:
            if template.name == template_name:
                template_group = template.template_group
                break
    
    # Use the shared transaction grouping logic from TradeHelper
    grouping = group_transactions(db_trade.transactions)
    
    # Build leg information from entry legs (sorted by strike ascending)
    legs = []
    for entry_leg in sorted(grouping.entry_legs, key=lambda l: l.strike):
        legs.append(LegInfo(
            strike=entry_leg.strike,
            right='CALL' if entry_leg.sectype == 'C' else 'PUT',
            action=entry_leg.action,
            expiration=entry_leg.expiration.strftime('%Y-%m-%d') if entry_leg.expiration else None
        ))
    
    # Calculate prices normalized to per-contract basis
    trade_amount = grouping.trade_amount
    entry_price = grouping.total_entry_premium / trade_amount if trade_amount > 0 else 0.0
    close_price = grouping.total_exit_premium / trade_amount if trade_amount > 0 else 0.0
    
    # Get expiration
    expiration = grouping.expiration.strftime('%Y-%m-%d') if grouping.expiration else None
    
    # Get entry time - prefer openDate, fallback to first transaction timestamp
    entry_time = None
    if db_trade.openDate:
        entry_time = db_trade.openDate.isoformat() + 'Z'
    elif db_trade.transactions:
        # Fallback: use timestamp of first transaction
        sorted_tx = sorted(db_trade.transactions, key=lambda t: t.id)
        if sorted_tx and sorted_tx[0].timestamp:
            entry_time = sorted_tx[0].timestamp.isoformat() + 'Z'
    
    # Get close time - prefer closeDate, fallback to last exit transaction timestamp
    close_time = None
    if db_trade.closeDate:
        close_time = db_trade.closeDate.isoformat() + 'Z'
    elif db_trade.status in ['CLOSED', 'EXPIRED'] and grouping.exit_legs:
        # Fallback: use timestamp of last exit transaction
        latest_timestamp = None
        for tx in db_trade.transactions:
            # Check if this is an exit transaction (appears after entry)
            if tx.timestamp and (latest_timestamp is None or tx.timestamp > latest_timestamp):
                latest_timestamp = tx.timestamp
        if latest_timestamp:
            close_time = latest_timestamp.isoformat() + 'Z'
    
    return TradeInfo(
        id=db_trade.id,
        template_name=db_trade.template_name or db_trade.strategy or 'Unknown',
        template_group=template_group,
        strategy=db_trade.strategy or 'Unknown',
        account=db_trade.account or '',
        symbol=db_trade.symbol or '',
        status=db_trade.status,
        entry_price=_sanitize_float(round(entry_price, 2)) if entry_price != 0 else None,
        current_price=_sanitize_float(round(close_price, 2)) if close_price != 0 else None,
        current_pnl=_sanitize_float(db_trade.realizedPNL),
        amount=trade_amount,
        entry_time=entry_time,
        close_time=close_time,
        fees=round(grouping.total_fees, 2) if grouping.total_fees > 0 else None,
        commission=round(grouping.total_commission, 2) if grouping.total_commission > 0 else None,
        legs=legs,
        expiration=expiration
    )


def _managed_trade_to_info(
    managed_trade: ManagedTrade, 
    transactions: list | None = None,
    db_realized_pnl: float | None = None,
    db_open_date: any = None,
    db_close_date: any = None
) -> TradeInfo:
    """Convert a ManagedTrade to TradeInfo for API response
    
    Args:
        managed_trade: The ManagedTrade object
        transactions: Optional list of transactions (pre-loaded from DB to avoid lazy-loading issues)
        db_realized_pnl: Optional realizedPNL from database (for closed trades)
        db_open_date: Optional openDate from database (fresh from DB)
        db_close_date: Optional closeDate from database (fresh from DB)
    """
    
    # Build leg information (sorted by strike ascending)
    legs = []
    for leg in sorted(managed_trade.current_legs, key=lambda l: l.strike):
        legs.append(LegInfo(
            strike=leg.strike,
            right='CALL' if leg.right == OptionRight.CALL else 'PUT',
            action='BUY' if leg.action == OrderAction.BUY else 'SELL',
            expiration=leg.expiration.strftime('%Y-%m-%d') if leg.expiration else None
        ))
    
    # Get expiration from legs or entry order
    expiration = None
    if managed_trade.current_legs and len(managed_trade.current_legs) > 0:
        exp_date = managed_trade.current_legs[0].expiration
        if exp_date:
            expiration = exp_date.strftime('%Y-%m-%d')
    
    # Calculate fees and commission from transactions first (needed for PNL)
    # Use provided transactions list or fallback to trade.transactions (may fail if detached)
    total_fees = 0.0
    total_commission = 0.0
    tx_list = transactions if transactions is not None else (managed_trade.trade.transactions if managed_trade.trade else [])
    for tx in tx_list:
        total_fees += tx.fee or 0.0
        total_commission += tx.commission or 0.0
    
    # Get the actual trade amount from the entry order (this is the real quantity used)
    # Fall back to template amount if entry order is not available
    trade_amount = 1
    if managed_trade.entryOrder and managed_trade.entryOrder.quantity:
        trade_amount = managed_trade.entryOrder.quantity
    elif managed_trade.template:
        trade_amount = managed_trade.template.amount
    
    # Calculate current PNL
    current_pnl = None
    # For closed/expired trades, use realizedPNL from database (already includes fees)
    if managed_trade.status in [TradeStatus.CLOSED, TradeStatus.EXPIRED]:
        # Prefer db_realized_pnl (fresh from DB) over managed_trade.trade.realizedPNL (may be stale)
        if db_realized_pnl is not None:
            current_pnl = db_realized_pnl
        elif managed_trade.trade and managed_trade.trade.realizedPNL is not None:
            current_pnl = managed_trade.trade.realizedPNL
    # For open/new trades, calculate from entry and current price
    # Only calculate if we have a valid current price (not None and not 0)
    else:
        entry_price_str = f'{managed_trade.entry_price:.2f}' if managed_trade.entry_price is not None else 'None'
        current_price_str = f'{managed_trade.current_price:.2f}' if managed_trade.current_price is not None else 'None'
        logger.debug(
            f'Trade {managed_trade.trade.id if managed_trade.trade else "?"}: '
            f'status={managed_trade.status}, entry_price={entry_price_str}, '
            f'current_price={current_price_str}'
        )
        # Calculate P&L if we have valid entry and current prices
        # Note: current_price can be 0 for worthless positions near expiration
        if managed_trade.entry_price is not None and managed_trade.current_price is not None:
            # Use is_credit_trade() method from template to determine calculation
            # For credit trades: profit when current price < entry price (we want to buy back cheaper)
            # For debit trades: profit when current price > entry price (we want to sell higher)
            if managed_trade.template and managed_trade.template.is_credit_trade():
                current_pnl = (managed_trade.entry_price - managed_trade.current_price) * 100 * trade_amount
            else:
                current_pnl = (managed_trade.current_price - managed_trade.entry_price) * 100 * trade_amount
            
            # Subtract fees and commissions from PNL to get net result
            current_pnl -= (total_fees + total_commission)
    
    # Get entry time - prefer fresh db_open_date, then managed_trade.trade.openDate, then first transaction
    entry_time = None
    if db_open_date:
        entry_time = db_open_date.isoformat() + 'Z'
    elif managed_trade.trade and managed_trade.trade.openDate:
        entry_time = managed_trade.trade.openDate.isoformat() + 'Z'
    elif tx_list:
        # Fallback: use timestamp of first transaction as entry time
        sorted_tx = sorted(tx_list, key=lambda t: t.id)
        if sorted_tx and sorted_tx[0].timestamp:
            entry_time = sorted_tx[0].timestamp.isoformat() + 'Z'
    
    # Get close time from trade record or calculate from transactions
    close_time = None
    close_price = None
    if managed_trade.status in [TradeStatus.CLOSED, TradeStatus.EXPIRED]:
        # Prefer fresh db_close_date, then managed_trade.trade.closeDate
        if db_close_date:
            close_time = db_close_date.isoformat() + 'Z'
        elif managed_trade.trade and managed_trade.trade.closeDate:
            close_time = managed_trade.trade.closeDate.isoformat() + 'Z'
        
        # Calculate close price and time from transactions if we have them
        # Use provided transactions list or fallback to trade.transactions
        tx_for_close = transactions if transactions is not None else (managed_trade.trade.transactions if managed_trade.trade else [])
        if tx_for_close:
            sorted_transactions = sorted(tx_for_close, key=lambda t: t.id)
            # Track contracts per leg to properly identify entry vs exit transactions
            # This is needed because with quantity > 1, each fill may be a separate transaction
            leg_contracts: dict[tuple, int] = {}  # leg_key -> total entry contracts
            exit_transactions = []
            
            # First pass: determine the trade quantity by counting contracts per leg
            for tx in sorted_transactions:
                if tx.strike and tx.sectype:
                    leg_key = (tx.strike, tx.sectype)
                    if leg_key not in leg_contracts:
                        leg_contracts[leg_key] = tx.contracts or 1
                    else:
                        leg_contracts[leg_key] += tx.contracts or 1
            
            # The trade quantity is the max contracts seen divided by 2 (entry + exit)
            # For a trade with 4 contracts and 4 legs, each leg has 8 transactions total
            trade_quantity = max(leg_contracts.values()) // 2 if leg_contracts else 1
            
            # Second pass: classify transactions as entry or exit
            # Entry transactions account for the first 'trade_quantity' contracts per leg
            leg_entry_count: dict[tuple, int] = {}  # leg_key -> contracts already counted as entry
            
            for tx in sorted_transactions:
                if tx.strike and tx.sectype:
                    leg_key = (tx.strike, tx.sectype)
                    tx_contracts = tx.contracts or 1
                    
                    if leg_key not in leg_entry_count:
                        leg_entry_count[leg_key] = 0
                    
                    # Check how many more contracts we need for entry
                    remaining_entry = trade_quantity - leg_entry_count[leg_key]
                    
                    if remaining_entry >= tx_contracts:
                        # All contracts in this transaction are entry
                        leg_entry_count[leg_key] += tx_contracts
                    elif remaining_entry > 0:
                        # Partial entry, partial exit (rare case)
                        leg_entry_count[leg_key] += remaining_entry
                        exit_transactions.append(tx)
                    else:
                        # All contracts are exit
                        exit_transactions.append(tx)
            
            # Calculate close price from exit transactions
            if exit_transactions:
                close_price = 0.0
                latest_timestamp = None
                for tx in exit_transactions:
                    tx_type_upper = (tx.type or '').upper()
                    if tx_type_upper == 'BUY':
                        close_price -= tx.price or 0.0
                    else:
                        close_price += tx.price or 0.0
                    # Track latest timestamp for close time
                    if tx.timestamp and (latest_timestamp is None or tx.timestamp > latest_timestamp):
                        latest_timestamp = tx.timestamp
                
                close_price = round(close_price, 2)
                
                # Use transaction timestamp as close time if not set
                if close_time is None and latest_timestamp:
                    close_time = latest_timestamp.isoformat() + 'Z'
    
    # For open trades, use current_price
    # Note: current_price can legitimately be 0 for worthless positions near expiration
    display_current_price = close_price if close_price is not None else managed_trade.current_price
    
    # Sanitize float values for JSON serialization (NaN values are not JSON compliant)
    sanitized_entry_price = _sanitize_float(managed_trade.entry_price)
    sanitized_current_price = _sanitize_float(display_current_price)
    sanitized_pnl = _sanitize_float(round(current_pnl, 2)) if current_pnl is not None else None
    
    return TradeInfo(
        id=managed_trade.trade.id if managed_trade.trade else 0,
        template_name=managed_trade.template.name if managed_trade.template else 'Unknown',
        template_group=managed_trade.template.template_group if managed_trade.template else None,
        strategy=managed_trade.template.strategy if managed_trade.template else 'Unknown',
        account=managed_trade.account or '',
        symbol=managed_trade.trade.symbol if managed_trade.trade else '',
        status=managed_trade.status,
        entry_price=sanitized_entry_price,
        current_price=sanitized_current_price,
        current_pnl=sanitized_pnl,
        amount=trade_amount,
        entry_time=entry_time,
        close_time=close_time,
        fees=round(total_fees, 2) if total_fees > 0 else None,
        commission=round(total_commission, 2) if total_commission > 0 else None,
        legs=legs,
        expiration=expiration
    )


@router.get('/trades/', response_model=List[TradeInfo])
async def get_trades(
    status: Optional[str] = Query(None, description='Filter by trade status (NEW, OPEN, CLOSED, EXPIRED)'),
    strategy: Optional[str] = Query(None, description='Filter by strategy name'),
    template_group: Optional[str] = Query(None, description='Filter by template group'),
    account: Optional[str] = Query(None, description='Filter by account'),
    time_range: TimeRange = Query(TimeRange.TODAY, description='Time range filter'),
    start_date: Optional[str] = Query(None, description='Start date for custom time range (YYYY-MM-DD)'),
    end_date: Optional[str] = Query(None, description='End date for custom time range (YYYY-MM-DD)')
) -> List[TradeInfo]:
    """
    Get list of trades with optional filters.
    
    Returns managed trades from TradeManager and closed/expired trades from database.
    """
    from sqlalchemy import select

    import optrabot.config as optrabotcfg
    try:
        trade_manager = TradeManager()
        managed_trades = trade_manager.getManagedTrades()
        
        # Get all templates for looking up template_group
        config: optrabotcfg.Config = optrabotcfg.appConfig
        all_templates = config.getTemplates() if config else []
        
        # Track IDs of managed trades to avoid duplicates
        managed_trade_ids = {mt.trade.id for mt in managed_trades if mt.trade and mt.trade.id}
        logger.debug(f"Managed trade IDs for transaction loading: {managed_trade_ids}")
        
        # Get time range start for filtering (handle custom date range)
        if time_range == TimeRange.CUSTOM and start_date:
            try:
                time_range_start = datetime.strptime(start_date, '%Y-%m-%d').replace(
                    hour=0, minute=0, second=0, microsecond=0,
                    tzinfo=pytz.timezone('US/Eastern')
                )
            except ValueError:
                time_range_start = None
        else:
            time_range_start = _get_time_range_start(time_range)
        
        # Get time range end for custom filtering
        time_range_end = None
        if time_range == TimeRange.CUSTOM and end_date:
            try:
                time_range_end = datetime.strptime(end_date, '%Y-%m-%d').replace(
                    hour=23, minute=59, second=59, microsecond=999999,
                    tzinfo=pytz.timezone('US/Eastern')
                )
            except ValueError:
                time_range_end = None
        
        result = []
        
        # Use a session to load transactions for managed trades
        with SessionLocal() as session:
            # Pre-load transactions and realizedPNL for all managed trades from database directly
            trade_transactions: dict[int, list] = {}
            trade_realized_pnl: dict[int, float | None] = {}
            trade_open_dates: dict[int, any] = {}
            trade_close_dates: dict[int, any] = {}
            if managed_trade_ids:
                # Query transactions directly instead of using relationship
                from optrabot.models import Transaction as TransactionModel
                tx_query = select(TransactionModel).where(
                    TransactionModel.tradeid.in_(managed_trade_ids)
                )
                all_transactions = session.scalars(tx_query).all()
                logger.debug(f'Loaded {len(all_transactions)} transactions for {len(managed_trade_ids)} trades')
                
                # Group by trade ID (ensure integer keys)
                for tx in all_transactions:
                    trade_id_key = int(tx.tradeid)
                    if trade_id_key not in trade_transactions:
                        trade_transactions[trade_id_key] = []
                    trade_transactions[trade_id_key].append(tx)
                
                for trade_id, txs in trade_transactions.items():
                    logger.debug(f'Trade {trade_id} has {len(txs)} transactions')
                
                # Also load realizedPNL, openDate and closeDate from database
                pnl_query = select(TradeModel.id, TradeModel.realizedPNL, TradeModel.openDate, TradeModel.closeDate).where(
                    TradeModel.id.in_(managed_trade_ids)
                )
                for row in session.execute(pnl_query):
                    trade_realized_pnl[row[0]] = row[1]
                    trade_open_dates[row[0]] = row[2]
                    trade_close_dates[row[0]] = row[3]
            
            # First, add active managed trades
            for managed_trade in managed_trades:
                # Apply status filter
                if status and managed_trade.status != status:
                    continue
                
                # Apply strategy filter
                if strategy and managed_trade.template and managed_trade.template.strategy != strategy:
                    continue
                
                # Apply template group filter
                if template_group:
                    trade_group = managed_trade.template.template_group if managed_trade.template else None
                    if template_group == 'none':
                        if trade_group is not None:
                            continue
                    elif trade_group != template_group:
                        continue
                
                # Apply account filter
                if account and managed_trade.account != account:
                    continue
                
                # Apply time range filter
                if time_range_start and managed_trade.trade:
                    # Get trade time - use openDate if available, otherwise include NEW trades
                    trade_time = managed_trade.trade.openDate
                    if trade_time is None:
                        # NEW trades without openDate should be included (they're current)
                        pass
                    else:
                        # Make sure we're comparing timezone-aware datetimes
                        if trade_time.tzinfo is None:
                            trade_time = pytz.UTC.localize(trade_time)
                        # Convert to same timezone for comparison
                        time_range_start_utc = time_range_start.astimezone(pytz.UTC)
                        trade_time_utc = trade_time.astimezone(pytz.UTC)
                        if trade_time_utc < time_range_start_utc:
                            continue
                        # Check end date for custom range
                        if time_range_end:
                            time_range_end_utc = time_range_end.astimezone(pytz.UTC)
                            if trade_time_utc > time_range_end_utc:
                                continue
                
                # Get transactions and realizedPNL from preloaded data
                trade_id = managed_trade.trade.id if managed_trade.trade else None
                transactions = trade_transactions.get(trade_id, []) if trade_id else []
                db_pnl = trade_realized_pnl.get(trade_id) if trade_id else None
                db_open_date = trade_open_dates.get(trade_id) if trade_id else None
                db_close_date = trade_close_dates.get(trade_id) if trade_id else None
                logger.debug(f'Trade {trade_id}: found {len(transactions)} preloaded transactions, db_pnl={db_pnl}')
                result.append(_managed_trade_to_info(managed_trade, transactions, db_pnl, db_open_date, db_close_date))
            # Build query for closed/expired trades
            query = select(TradeModel).where(
                TradeModel.status.in_([TradeStatus.CLOSED, TradeStatus.EXPIRED])
            )
            
            # Apply filters
            if status:
                query = query.where(TradeModel.status == status)
            if strategy:
                query = query.where(TradeModel.strategy == strategy)
            if account:
                query = query.where(TradeModel.account == account)
            if time_range_start:
                # Filter by openDate (when the trade was opened)
                time_range_start_utc = time_range_start.astimezone(pytz.UTC)
                query = query.where(TradeModel.openDate >= time_range_start_utc)
            if time_range_end:
                # Filter by openDate end for custom range
                time_range_end_utc = time_range_end.astimezone(pytz.UTC)
                query = query.where(TradeModel.openDate <= time_range_end_utc)
            
            db_trades = session.scalars(query).all()
            
            for db_trade in db_trades:
                # Skip if already in managed trades
                if db_trade.id in managed_trade_ids:
                    continue
                result.append(_db_trade_to_info(db_trade, all_templates))
        
        # Sort by ID descending (newest first)
        result.sort(key=lambda x: x.id, reverse=True)
        
        return result
        
    except Exception as e:
        logger.error(f'Error fetching trades: {e}')
        raise HTTPException(status_code=500, detail=str(e))


@router.get('/trades/filters')
async def get_trade_filters():
    """
    Get available filter options based on current and historical trades.
    
    Returns unique values for strategy, template_group, and account.
    """
    from sqlalchemy import distinct, select
    try:
        trade_manager = TradeManager()
        managed_trades = trade_manager.getManagedTrades()
        
        strategies = set()
        template_groups = set()
        accounts = set()
        
        # Get filters from active managed trades
        for managed_trade in managed_trades:
            if managed_trade.template:
                if managed_trade.template.strategy:
                    strategies.add(managed_trade.template.strategy)
                if managed_trade.template.template_group:
                    template_groups.add(managed_trade.template.template_group)
            if managed_trade.account:
                accounts.add(managed_trade.account)
        
        # Also get filters from closed/expired trades in database
        with SessionLocal() as session:
            # Get distinct strategies
            db_strategies = session.scalars(
                select(distinct(TradeModel.strategy)).where(TradeModel.strategy.isnot(None))
            ).all()
            strategies.update(db_strategies)
            
            # Get distinct accounts
            db_accounts = session.scalars(
                select(distinct(TradeModel.account)).where(TradeModel.account.isnot(None))
            ).all()
            accounts.update(db_accounts)
        
        return {
            'strategies': sorted(list(strategies)),
            'template_groups': sorted(list(template_groups)),
            'accounts': sorted(list(accounts)),
            'statuses': [TradeStatus.NEW, TradeStatus.OPEN, TradeStatus.CLOSED, TradeStatus.EXPIRED],
            'time_ranges': [
                {'value': 'today', 'label': 'Heute'},
                {'value': 'yesterday', 'label': 'Gestern'},
                {'value': 'this_week', 'label': 'Diese Woche'},
                {'value': 'all', 'label': 'Alle'}
            ]
        }
        
    except Exception as e:
        logger.error(f'Error fetching trade filters: {e}')
        raise HTTPException(status_code=500, detail=str(e))


@router.post('/trades/close', response_model=CloseTradeResponse)
async def close_trade(request: CloseTradeRequest) -> CloseTradeResponse:
    """
    Close a trade manually.
    
    This will:
    1. Cancel any existing Take Profit and Stop Loss orders
    2. Create a closing order at the current mid price
    3. Monitor and adjust the limit price until filled
    
    If trigger_flow is False, no flow events (MANUAL_CLOSE) will be fired.
    This is useful for emergency closings where you don't want flows to trigger.
    """
    try:
        trade_manager = TradeManager()
        
        # Find the managed trade
        managed_trade = None
        for mt in trade_manager.getManagedTrades():
            if mt.trade and mt.trade.id == request.trade_id:
                managed_trade = mt
                break
        
        if not managed_trade:
            raise HTTPException(status_code=404, detail=f'Trade {request.trade_id} not found')
        
        # Check if trade can be closed
        if managed_trade.status not in [TradeStatus.NEW, TradeStatus.OPEN]:
            raise HTTPException(
                status_code=400, 
                detail=f'Trade {request.trade_id} cannot be closed (status: {managed_trade.status})'
            )
        
        # Close the trade
        await trade_manager.close_trade_manually(
            managed_trade, 
            trigger_flow=request.trigger_flow
        )
        
        return CloseTradeResponse(
            success=True,
            message=f'Trade {request.trade_id} closing initiated',
            trade_id=request.trade_id
        )
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f'Error closing trade {request.trade_id}: {e}')
        raise HTTPException(status_code=500, detail=str(e)) from e


@router.post('/trades/mark-as-closed', response_model=MarkAsClosedResponse)
async def mark_trade_as_closed(request: MarkAsClosedRequest) -> MarkAsClosedResponse:
    """
    Mark a trade as closed without placing a broker order (OTB-336).
    
    This is used when the user has already manually closed the trade at the broker
    and just needs to update the trade status in OptraBot.
    
    The user provides:
    - close_price: The total closing premium for the trade
    - fees: The total fees/commissions for closing
    
    OptraBot will:
    1. Cancel any existing Take Profit and Stop Loss orders (if they exist)
    2. Create closing transactions with averaged prices for each leg
    3. Update the trade status to CLOSED
    4. Calculate and store the realized P&L
    
    Note: No flow events are triggered, since this is only a bookkeeping operation.
    """
    try:
        trade_manager = TradeManager()
        
        # Find the managed trade
        managed_trade = None
        for mt in trade_manager.getManagedTrades():
            if mt.trade and mt.trade.id == request.trade_id:
                managed_trade = mt
                break
        
        if not managed_trade:
            raise HTTPException(status_code=404, detail=f'Trade {request.trade_id} not found')
        
        # Check if trade can be closed
        if managed_trade.status not in [TradeStatus.NEW, TradeStatus.OPEN]:
            raise HTTPException(
                status_code=400, 
                detail=f'Trade {request.trade_id} cannot be marked as closed (status: {managed_trade.status})'
            )
        
        # Mark the trade as closed with the provided close price and fees
        # No flow events are triggered for bookkeeping-only operations
        realized_pnl = await trade_manager.mark_trade_as_closed(
            managed_trade,
            close_price=request.close_price,
            fees=request.fees
        )
        
        return MarkAsClosedResponse(
            success=True,
            message=f'Trade {request.trade_id} marked as closed',
            trade_id=request.trade_id,
            realized_pnl=realized_pnl
        )
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f'Error marking trade {request.trade_id} as closed: {e}')
        raise HTTPException(status_code=500, detail=str(e)) from e


class InstantTradeRequest(BaseModel):
    """Request model for starting an instant trade"""
    template_name: str
    amount: Optional[int] = None  # Override template amount if provided


class InstantTradeResponse(BaseModel):
    """Response model for instant trade request"""
    success: bool
    message: str
    template_name: str


def _get_condition_failure_reason(template: Template, processor) -> str:
    """
    Determine the specific reason why template conditions failed.
    This provides user-friendly error messages for the UI.
    """
    from optrabot.broker.brokerfactory import BrokerFactory
    from optrabot.trademanager import TradeManager

    # Check template group exclusivity
    if template.template_group is not None:
        trade_manager = TradeManager()
        active_trades = trade_manager.getManagedTrades()
        for trade in active_trades:
            if trade.isActive() and trade.template.template_group == template.template_group:
                return f'Template group "{template.template_group}" already has an active trade (Template: {trade.template.name})'
    
    # Check VIX conditions
    if template.vix_max or template.vix_min:
        broker = BrokerFactory().getBrokerConnectorByAccount(template.account)
        if broker is None:
            return f'No broker connection available for account {template.account}'
        
        try:
            from optrabot.symbolinfo import symbol_infos
            vix_price = broker.getLastPrice(symbol_infos['VIX'].symbol)
            if vix_price:
                if template.vix_max and vix_price > template.vix_max:
                    return f'VIX ({vix_price:.2f}) exceeds maximum ({template.vix_max})'
                if template.vix_min and vix_price < template.vix_min:
                    return f'VIX ({vix_price:.2f}) below minimum ({template.vix_min})'
        except Exception:
            return 'No VIX price data available'
    
    return 'Template conditions not met'


async def _send_condition_failure_notification(template: Template, reason: str) -> None:
    """
    Send Telegram notification when template conditions are not met.
    """
    from optrabot.tradinghubclient import NotificationType, TradinghubClient
    
    message = (
        f'⚠️ Trade blocked for template *{template.name}*\n'
        f'*Strategy:* {template.strategy}\n'
        f'*Account:* {template.account}\n'
        f'*Reason:* {reason}'
    )
    
    await TradinghubClient().send_notification(NotificationType.WARN, message)


@router.post('/trades/instant', response_model=InstantTradeResponse)
async def start_instant_trade(request: InstantTradeRequest) -> InstantTradeResponse:
    """
    Start an instant trade using the specified template.
    
    This triggers the template processing similar to a time-based trigger
    or an external signal from the OptraBot Hub.
    """
    try:
        config: optrabotcfg.Config = optrabotcfg.appConfig
        templates = config.getTemplates()
        
        # Find the requested template
        template: Template | None = None
        for t in templates:
            if t.name == request.template_name:
                template = t
                break
        
        if template is None:
            raise HTTPException(
                status_code=404, 
                detail=f'Template "{request.template_name}" not found'
            )
        
        if not template.is_enabled():
            raise HTTPException(
                status_code=400,
                detail=f'Template "{request.template_name}" is disabled'
            )
        
        # Override amount if provided in request
        original_amount = template.amount
        if request.amount is not None and request.amount > 0:
            template.amount = request.amount
            logger.debug(f'Overriding template amount from {original_amount} to {request.amount}')
        
        # Create signal data with current timestamp
        signal_data = SignalData(
            timestamp=datetime.now().astimezone(pytz.UTC), 
            close=0, 
            strike=0
        )
        
        # Create a copy of the template to avoid race conditions
        template_copy = copy.deepcopy(template)
        if request.amount is not None and request.amount > 0:
            template_copy.amount = request.amount
        
        # Check template conditions BEFORE starting background task
        # This allows immediate feedback to the UI
        template_processor = TemplateProcessor()
        try:
            specific_processor = template_processor.createTemplateProcessor(template_copy)
        except ValueError as e:
            raise HTTPException(
                status_code=400,
                detail=f'Failed to create template processor: {str(e)}'
            ) from e
        
        if not specific_processor.check_conditions():
            # Get specific reason for condition failure
            condition_reason = _get_condition_failure_reason(template_copy, specific_processor)
            
            # Send Telegram notification about condition failure
            await _send_condition_failure_notification(template_copy, condition_reason)
            
            raise HTTPException(
                status_code=400,
                detail=condition_reason
            )
        
        # Process the template in background task
        # OTB-269: processTemplate now waits for entry completion, so we run it
        # as a background task to avoid blocking the HTTP response
        async def process_template_background():
            try:
                await template_processor.processTemplate(template_copy, signal_data)
            except Exception as e:
                logger.error(f'Background template processing failed for {request.template_name}: {e}')
        
        # Start processing in background - don't await
        asyncio.create_task(process_template_background())
        
        logger.info(f'Instant trade started for template {request.template_name} with amount {request.amount or original_amount}')
        
        return InstantTradeResponse(
            success=True,
            message=f'Trade for template "{request.template_name}" initiated successfully',
            template_name=request.template_name
        )
        
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f'Error starting instant trade for {request.template_name}: {e}')
        raise HTTPException(status_code=500, detail=str(e)) from e
