from fastapi import APIRouter, HTTPException, Depends
from sqlalchemy.orm import Session, selectinload
from typing import Dict, Optional, Any, List
from datetime import datetime, timezone

from app.db.database import get_db
from app.models.models import Simulation, Trade, Position, TradeAction, TradeMetadata
from app.services.risk import validate_order
from pydantic import BaseModel

router = APIRouter(prefix="/trading", tags=["Trading"])


# Pydantic models for request/response
class TradeRequest(BaseModel):
    action: str
    quantity: float
    price: float
    symbol: str = "XAU/USD"
    notes: Optional[str] = None
    stop_loss: Optional[float] = None
    take_profit: Optional[float] = None
    source: Optional[str] = None
    platform: Optional[str] = None
    risk_percent: Optional[float] = None
    entry_time: Optional[str] = None


class PortfolioState(BaseModel):
    cash: float
    initial_capital: float
    position: Optional[Dict[str, Any]] = None
    trades: List[Dict[str, Any]]
    equity_history: List[Dict[str, Any]]
    total_pnl: float
    total_pnl_percent: float


def get_or_create_simulation(db: Session, user_id: str = "default") -> Simulation:
    """Get existing simulation or create a new one"""
    simulation = db.query(Simulation).filter(Simulation.user_id == user_id).first()
    
    if not simulation:
        simulation = Simulation(
            user_id=user_id,
            symbol="XAU/USD",
            initial_capital=100000.0,
            current_capital=100000.0,
            total_pnl=0.0,
            total_pnl_percent=0.0
        )
        db.add(simulation)
        db.commit()
        db.refresh(simulation)
    
    return simulation


def _compute_equity_at_price(simulation: Simulation, price: float, db: Session) -> float:
    """Calculate equity based on current position and price"""
    position = db.query(Position).filter(
        Position.simulation_id == simulation.id
    ).first()
    
    qty = position.quantity if position else 0.0
    return float(simulation.current_capital + qty * price)


def get_portfolio_state_from_db(simulation: Simulation, db: Session) -> PortfolioState:
    """Convert DB simulation to portfolio state"""
    # Get current position
    position = db.query(Position).filter(
        Position.simulation_id == simulation.id
    ).first()
    
    position_dict = None
    if position:
        position_dict = {
            "symbol": position.symbol,
            "quantity": position.quantity,
            "avg_price": position.avg_price,
            "current_price": position.current_price,
            "unrealized_pnl": position.unrealized_pnl,
            "unrealized_pnl_percent": position.unrealized_pnl_percent
        }
    
    # Get all trades
    trades = db.query(Trade).options(selectinload(Trade.details)).filter(
        Trade.simulation_id == simulation.id
    ).order_by(Trade.timestamp).all()

    trades_list = []
    for trade in trades:
        details = trade.details
        trades_list.append({
            "id": trade.id,
            "action": trade.action.value,
            "quantity": trade.quantity,
            "price": trade.price,
            "total": trade.total,
            "pnl": trade.pnl,
            "timestamp": int(trade.timestamp.timestamp()) if trade.timestamp else None,
            "stop_loss": details.stop_loss if details else None,
            "take_profit": details.take_profit if details else None,
            "notes": details.notes if details else None,
            "source": details.source if details else None,
            "platform": details.platform if details else None,
            "risk_percent": details.risk_percent if details else None,
            "entry_time": details.entry_time.isoformat() if details and details.entry_time else None,
        })
    
    # Build equity history from trades
    equity_history = []
    running_equity = simulation.initial_capital
    for trade in trades:
        if trade.action == TradeAction.SELL and trade.pnl:
            running_equity += trade.pnl
        equity_history.append({
            "time": int(trade.timestamp.timestamp()) if trade.timestamp else 0,
            "equity": running_equity
        })
    
    return PortfolioState(
        cash=simulation.current_capital,
        initial_capital=simulation.initial_capital,
        position=position_dict,
        trades=trades_list,
        equity_history=equity_history,
        total_pnl=simulation.total_pnl,
        total_pnl_percent=simulation.total_pnl_percent
    )


@router.post("/execute")
async def execute_trade(
    trade_request: TradeRequest,
    db: Session = Depends(get_db),
    user_id: str = "default"
):
    """
    Execute a trade and persist to database.
    
    - Validates risk rules
    - Updates cash/position in DB
    - Records trade with timestamp
    - Returns updated portfolio state
    """
    try:
        action = trade_request.action.upper()
        quantity = trade_request.quantity
        price = trade_request.price
        
        if action not in ["BUY", "SELL"]:
            raise HTTPException(status_code=400, detail="Action must be BUY or SELL")
        
        # Get or create simulation
        simulation = get_or_create_simulation(db, user_id)
        
        # Build state dict for risk validation
        position = db.query(Position).filter(
            Position.simulation_id == simulation.id
        ).first()
        
        state_dict = {
            "cash": simulation.current_capital,
            "position": {
                "quantity": position.quantity,
                "avg_price": position.avg_price
            } if position else None
        }
        
        # Risk validation
        try:
            validate_order(state_dict, action, quantity, price)
        except ValueError as ve:
            raise HTTPException(status_code=400, detail=str(ve))
        
        total = quantity * price
        pnl = None
        
        if action == "BUY":
            if total > simulation.current_capital:
                raise HTTPException(status_code=400, detail="Insufficient funds")
            
            simulation.current_capital -= total
            
            if not position:
                # Create new position
                position = Position(
                    simulation_id=simulation.id,
                    symbol=trade_request.symbol,
                    quantity=quantity,
                    avg_price=price,
                    current_price=price,
                    unrealized_pnl=0.0,
                    unrealized_pnl_percent=0.0
                )
                db.add(position)
            else:
                # Update existing position (average up)
                new_qty = position.quantity + quantity
                new_avg = (position.avg_price * position.quantity + price * quantity) / new_qty
                position.quantity = new_qty
                position.avg_price = new_avg
                position.current_price = price
        
        elif action == "SELL":
            if not position or quantity > position.quantity:
                raise HTTPException(status_code=400, detail="Insufficient position")
            
            simulation.current_capital += total
            pnl = (price - position.avg_price) * quantity
            
            # Update simulation totals
            simulation.total_pnl += pnl
            if simulation.initial_capital > 0:
                simulation.total_pnl_percent = (simulation.total_pnl / simulation.initial_capital) * 100
            
            position.quantity -= quantity
            
            if position.quantity == 0:
                # Close position
                db.delete(position)
                position = None
            else:
                position.current_price = price
        
        # Create trade record
        trade_timestamp = datetime.now(timezone.utc)
        trade = Trade(
            simulation_id=simulation.id,
            action=TradeAction[action],
            quantity=quantity,
            price=price,
            total=total,
            pnl=pnl,
            timestamp=trade_timestamp
        )
        db.add(trade)
        db.flush()

        entry_time_dt = None
        if trade_request.entry_time:
            try:
                entry_time_dt = datetime.fromisoformat(trade_request.entry_time)
                if entry_time_dt.tzinfo is None:
                    entry_time_dt = entry_time_dt.replace(tzinfo=timezone.utc)
            except ValueError:
                entry_time_dt = trade_timestamp

        metadata_fields = (
            trade_request.source,
            trade_request.platform,
            trade_request.notes,
            trade_request.stop_loss,
            trade_request.take_profit,
            trade_request.risk_percent,
            entry_time_dt,
        )

        if any(field is not None for field in metadata_fields):
            trade_metadata = TradeMetadata(
                trade_id=trade.id,
                source=trade_request.source,
                platform=trade_request.platform,
                notes=trade_request.notes,
                stop_loss=trade_request.stop_loss,
                take_profit=trade_request.take_profit,
                risk_percent=trade_request.risk_percent,
                entry_time=entry_time_dt,
            )
            db.add(trade_metadata)
        
        # Commit all changes
        db.commit()
        db.refresh(simulation)
        
        # Return updated portfolio state
        portfolio_state = get_portfolio_state_from_db(simulation, db)
        
        return {
            "trade": {
                "id": trade.id,
                "action": action,
                "quantity": quantity,
                "price": price,
                "total": total,
                "pnl": pnl,
                "timestamp": int(trade.timestamp.timestamp()) if trade.timestamp else None,
                "stop_loss": trade_request.stop_loss,
                "take_profit": trade_request.take_profit,
                "notes": trade_request.notes,
                "source": trade_request.source,
                "platform": trade_request.platform,
                "risk_percent": trade_request.risk_percent,
                "entry_time": entry_time_dt.isoformat() if entry_time_dt else None,
            },
            "portfolio": portfolio_state.dict()
        }
    
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=f"Trade execution failed: {str(e)}")


@router.get("/portfolio")
async def get_portfolio(
    db: Session = Depends(get_db),
    user_id: str = "default"
):
    """Get current portfolio state from database"""
    try:
        simulation = get_or_create_simulation(db, user_id)
        portfolio_state = get_portfolio_state_from_db(simulation, db)
        return portfolio_state.dict()
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to get portfolio: {str(e)}")


@router.post("/reset")
async def reset_simulation(
    db: Session = Depends(get_db),
    user_id: str = "default"
):
    """Reset simulation to initial state"""
    try:
        simulation = db.query(Simulation).filter(Simulation.user_id == user_id).first()
        
        if simulation:
            # Delete all trades and positions (cascade will handle this)
            db.delete(simulation)
            db.commit()
        
        # Create new simulation
        new_simulation = Simulation(
            user_id=user_id,
            symbol="XAU/USD",
            initial_capital=100000.0,
            current_capital=100000.0,
            total_pnl=0.0,
            total_pnl_percent=0.0
        )
        db.add(new_simulation)
        db.commit()
        db.refresh(new_simulation)
        
        portfolio_state = get_portfolio_state_from_db(new_simulation, db)
        
        return {
            "message": "Simulation reset successfully",
            "portfolio": portfolio_state.dict()
        }
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=f"Reset failed: {str(e)}")


@router.get("/history")
async def get_trade_history(
    db: Session = Depends(get_db),
    user_id: str = "default",
    limit: int = 100
):
    """Get trade history from database"""
    try:
        simulation = get_or_create_simulation(db, user_id)
        
        trades = db.query(Trade).options(selectinload(Trade.details)).filter(
            Trade.simulation_id == simulation.id
        ).order_by(Trade.timestamp.desc()).limit(limit).all()
        
        return [
            {
                "id": trade.id,
                "action": trade.action.value,
                "quantity": trade.quantity,
                "price": trade.price,
                "total": trade.total,
                "pnl": trade.pnl,
                "timestamp": int(trade.timestamp.timestamp()) if trade.timestamp else None,
                "stop_loss": trade.details.stop_loss if trade.details else None,
                "take_profit": trade.details.take_profit if trade.details else None,
                "notes": trade.details.notes if trade.details else None,
                "source": trade.details.source if trade.details else None,
                "platform": trade.details.platform if trade.details else None,
                "risk_percent": trade.details.risk_percent if trade.details else None,
                "entry_time": trade.details.entry_time.isoformat() if trade.details and trade.details.entry_time else None,
            }
            for trade in trades
        ]
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to get history: {str(e)}")


@router.get("/stats")
async def get_trading_stats(
    db: Session = Depends(get_db),
    user_id: str = "default"
):
    """Get trading statistics"""
    try:
        simulation = get_or_create_simulation(db, user_id)
        
        trades = db.query(Trade).filter(
            Trade.simulation_id == simulation.id
        ).all()
        
        total_trades = len(trades)
        winning_trades = sum(1 for t in trades if t.pnl and t.pnl > 0)
        losing_trades = sum(1 for t in trades if t.pnl and t.pnl < 0)
        
        total_profit = sum(t.pnl for t in trades if t.pnl and t.pnl > 0)
        total_loss = sum(abs(t.pnl) for t in trades if t.pnl and t.pnl < 0)
        
        win_rate = (winning_trades / total_trades * 100) if total_trades > 0 else 0
        profit_factor = (total_profit / total_loss) if total_loss > 0 else 0
        
        return {
            "total_trades": total_trades,
            "winning_trades": winning_trades,
            "losing_trades": losing_trades,
            "win_rate": round(win_rate, 2),
            "total_pnl": simulation.total_pnl,
            "total_pnl_percent": simulation.total_pnl_percent,
            "total_profit": total_profit,
            "total_loss": total_loss,
            "profit_factor": round(profit_factor, 2),
            "current_capital": simulation.current_capital,
            "initial_capital": simulation.initial_capital
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to get stats: {str(e)}")
