from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel, Field
from typing import List, Optional, Dict
import numpy as np

from ...optimization.portfolio_optimizer import PortfolioOptimizer
from ..app import get_portfolio_optimizer

router = APIRouter()

class OptimizationConstraints(BaseModel):
    min_weight: Optional[float] = Field(0.0, ge=0.0, le=1.0)
    max_weight: Optional[float] = Field(1.0, ge=0.0, le=1.0)
    target_return: Optional[float] = None
    max_risk: Optional[float] = None
    
class PortfolioOptimizationRequest(BaseModel):
    symbols: List[str]
    returns: List[List[float]]
    constraints: Optional[OptimizationConstraints] = None
    method: str = Field("mean_variance", pattern="^(mean_variance|min_variance|max_sharpe|risk_parity|black_litterman)$")

class PortfolioWeights(BaseModel):
    symbols: List[str]
    weights: List[float]
    expected_return: float
    expected_risk: float
    sharpe_ratio: float

@router.post("/portfolio", response_model=PortfolioWeights)
async def optimize_portfolio(
    request: PortfolioOptimizationRequest,
    optimizer: PortfolioOptimizer = Depends(get_portfolio_optimizer)
):
    """
    Optimize portfolio weights using various methods
    """
    try:
        # Convert returns to numpy array
        returns_array = np.array(request.returns)
        
        # Validate dimensions
        if len(request.symbols) != returns_array.shape[1]:
            raise HTTPException(
                status_code=400,
                detail="Number of symbols must match number of return columns"
            )
        
        # Run optimization
        result = optimizer.optimize(
            returns=returns_array,
            method=request.method,
            constraints=request.constraints.dict() if request.constraints else None
        )
        
        return PortfolioWeights(
            symbols=request.symbols,
            weights=result['weights'].tolist(),
            expected_return=float(result['expected_return']),
            expected_risk=float(result['expected_risk']),
            sharpe_ratio=float(result['sharpe_ratio'])
        )
        
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Optimization failed: {str(e)}")

@router.post("/efficient_frontier")
async def calculate_efficient_frontier(
    request: PortfolioOptimizationRequest,
    num_portfolios: int = 100,
    optimizer: PortfolioOptimizer = Depends(get_portfolio_optimizer)
):
    """
    Calculate the efficient frontier for a set of assets
    """
    try:
        returns_array = np.array(request.returns)
        
        frontier = optimizer.calculate_efficient_frontier(
            returns=returns_array,
            num_portfolios=num_portfolios
        )
        
        return {
            "symbols": request.symbols,
            "frontier": frontier
        }
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to calculate efficient frontier: {str(e)}")

@router.post("/rebalance")
async def suggest_rebalance(
    current_weights: Dict[str, float],
    target_weights: Dict[str, float],
    constraints: Optional[Dict[str, float]] = None
):
    """
    Suggest trades to rebalance portfolio from current to target weights
    """
    try:
        # Calculate differences
        trades = {}
        for symbol in target_weights:
            current = current_weights.get(symbol, 0.0)
            target = target_weights[symbol]
            diff = target - current
            
            if abs(diff) > 0.001:  # Ignore tiny differences
                trades[symbol] = diff
        
        return {
            "trades": trades,
            "total_turnover": sum(abs(t) for t in trades.values())
        }
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Rebalance calculation failed: {str(e)}")