"""Database connection and models using SQLAlchemy."""

from datetime import datetime
from decimal import Decimal
from enum import Enum
from pathlib import Path
from typing import Optional
from sqlalchemy import (
    create_engine, Column, Integer, String, Float, Boolean, DateTime, 
    Text, ForeignKey, JSON, Enum as SQLEnum, Numeric
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship, Session
from .config import get_config

Base = declarative_base()


class OrderType(str, Enum):
    """Order type enumeration."""
    MARKET = "market"
    LIMIT = "limit"
    STOP_LOSS = "stop_loss"
    TAKE_PROFIT = "take_profit"
    TRAILING_STOP = "trailing_stop"
    OCO = "oco"
    ICEBERG = "iceberg"


class OrderSide(str, Enum):
    """Order side enumeration."""
    BUY = "buy"
    SELL = "sell"


class OrderStatus(str, Enum):
    """Order status enumeration."""
    PENDING = "pending"
    OPEN = "open"
    PARTIALLY_FILLED = "partially_filled"
    FILLED = "filled"
    CANCELLED = "cancelled"
    REJECTED = "rejected"
    EXPIRED = "expired"


class TradeType(str, Enum):
    """Trade type enumeration."""
    SPOT = "spot"
    FUTURES = "futures"
    MARGIN = "margin"


class Exchange(Base):
    """Exchange configuration and credentials."""
    __tablename__ = "exchanges"
    
    id = Column(Integer, primary_key=True)
    name = Column(String(50), nullable=False, unique=True)
    api_key_encrypted = Column(Text)  # Encrypted API key
    api_secret_encrypted = Column(Text)  # Encrypted API secret
    sandbox = Column(Boolean, default=False)
    read_only = Column(Boolean, default=True)  # Read-only mode
    enabled = Column(Boolean, default=True)
    created_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    # Relationships
    trades = relationship("Trade", back_populates="exchange")
    orders = relationship("Order", back_populates="exchange")
    positions = relationship("Position", back_populates="exchange")


class Strategy(Base):
    """Strategy definitions and parameters."""
    __tablename__ = "strategies"
    
    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    description = Column(Text)
    strategy_type = Column(String(50))  # technical, momentum, grid, dca, etc.
    class_name = Column(String(100))  # Python class name
    parameters = Column(JSON)  # Strategy parameters
    timeframes = Column(JSON)  # Multi-timeframe configuration
    enabled = Column(Boolean, default=False)  # Available to Autopilot
    running = Column(Boolean, default=False)  # Currently running manually
    paper_trading = Column(Boolean, default=True)
    version = Column(String(20), default="1.0.0")
    schedule = Column(JSON)  # Scheduling configuration
    created_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    # Relationships
    trades = relationship("Trade", back_populates="strategy")
    backtest_results = relationship("BacktestResult", back_populates="strategy")


class Order(Base):
    """Order history with state tracking."""
    __tablename__ = "orders"
    
    id = Column(Integer, primary_key=True)
    exchange_id = Column(Integer, ForeignKey("exchanges.id"), nullable=False)
    strategy_id = Column(Integer, ForeignKey("strategies.id"), nullable=True)
    exchange_order_id = Column(String(100))  # Exchange's order ID
    symbol = Column(String(20), nullable=False)
    order_type = Column(SQLEnum(OrderType), nullable=False)
    side = Column(SQLEnum(OrderSide), nullable=False)
    status = Column(SQLEnum(OrderStatus), default=OrderStatus.PENDING)
    quantity = Column(Numeric(20, 8), nullable=False)
    price = Column(Numeric(20, 8))  # For limit orders
    filled_quantity = Column(Numeric(20, 8), default=0)
    average_fill_price = Column(Numeric(20, 8))
    fee = Column(Numeric(20, 8), default=0)
    trade_type = Column(SQLEnum(TradeType), default=TradeType.SPOT)
    leverage = Column(Integer, default=1)  # For futures/margin
    paper_trading = Column(Boolean, default=True)
    created_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    filled_at = Column(DateTime)
    
    # Relationships
    exchange = relationship("Exchange", back_populates="orders")
    strategy = relationship("Strategy")
    trades = relationship("Trade", back_populates="order")


class Trade(Base):
    """All executed trades (paper and live)."""
    __tablename__ = "trades"
    
    id = Column(Integer, primary_key=True)
    exchange_id = Column(Integer, ForeignKey("exchanges.id"), nullable=False)
    strategy_id = Column(Integer, ForeignKey("strategies.id"), nullable=True)
    order_id = Column(Integer, ForeignKey("orders.id"), nullable=True)
    symbol = Column(String(20), nullable=False)
    side = Column(SQLEnum(OrderSide), nullable=False)
    quantity = Column(Numeric(20, 8), nullable=False)
    price = Column(Numeric(20, 8), nullable=False)
    fee = Column(Numeric(20, 8), default=0)
    total = Column(Numeric(20, 8), nullable=False)  # quantity * price + fee
    trade_type = Column(SQLEnum(TradeType), default=TradeType.SPOT)
    paper_trading = Column(Boolean, default=True)
    timestamp = Column(DateTime, default=datetime.utcnow, nullable=False)
    
    # Relationships
    exchange = relationship("Exchange", back_populates="trades")
    strategy = relationship("Strategy", back_populates="trades")
    order = relationship("Order", back_populates="trades")


class Position(Base):
    """Current open positions (spot and futures)."""
    __tablename__ = "positions"
    
    id = Column(Integer, primary_key=True)
    exchange_id = Column(Integer, ForeignKey("exchanges.id"), nullable=False)
    symbol = Column(String(20), nullable=False)
    side = Column(String(10))  # long, short
    quantity = Column(Numeric(20, 8), nullable=False)
    entry_price = Column(Numeric(20, 8), nullable=False)
    current_price = Column(Numeric(20, 8))
    unrealized_pnl = Column(Numeric(20, 8), default=0)
    realized_pnl = Column(Numeric(20, 8), default=0)
    trade_type = Column(SQLEnum(TradeType), default=TradeType.SPOT)
    leverage = Column(Integer, default=1)
    margin = Column(Numeric(20, 8))
    paper_trading = Column(Boolean, default=True)
    opened_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    # Relationships
    exchange = relationship("Exchange", back_populates="positions")


class PortfolioSnapshot(Base):
    """Historical portfolio values."""
    __tablename__ = "portfolio_snapshots"
    
    id = Column(Integer, primary_key=True)
    total_value = Column(Numeric(20, 8), nullable=False)
    cash = Column(Numeric(20, 8), nullable=False)
    positions_value = Column(Numeric(20, 8), nullable=False)
    unrealized_pnl = Column(Numeric(20, 8), default=0)
    realized_pnl = Column(Numeric(20, 8), default=0)
    paper_trading = Column(Boolean, default=True)
    timestamp = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)


class MarketData(Base):
    """OHLCV historical data (multiple timeframes)."""
    __tablename__ = "market_data"
    
    id = Column(Integer, primary_key=True)
    exchange = Column(String(50), nullable=False)
    symbol = Column(String(20), nullable=False)
    timeframe = Column(String(10), nullable=False)  # 1m, 5m, 15m, 1h, 1d, etc.
    timestamp = Column(DateTime, nullable=False, index=True)
    open = Column(Numeric(20, 8), nullable=False)
    high = Column(Numeric(20, 8), nullable=False)
    low = Column(Numeric(20, 8), nullable=False)
    close = Column(Numeric(20, 8), nullable=False)
    volume = Column(Numeric(20, 8), nullable=False)
    



class BacktestResult(Base):
    """Backtesting results and metrics."""
    __tablename__ = "backtest_results"
    
    id = Column(Integer, primary_key=True)
    strategy_id = Column(Integer, ForeignKey("strategies.id"), nullable=False)
    start_date = Column(DateTime, nullable=False)
    end_date = Column(DateTime, nullable=False)
    initial_capital = Column(Numeric(20, 8), nullable=False)
    final_capital = Column(Numeric(20, 8), nullable=False)
    total_return = Column(Numeric(10, 4))  # Percentage
    sharpe_ratio = Column(Numeric(10, 4))
    sortino_ratio = Column(Numeric(10, 4))
    max_drawdown = Column(Numeric(10, 4))
    win_rate = Column(Numeric(10, 4))
    total_trades = Column(Integer, default=0)
    parameters = Column(JSON)  # Parameters used in backtest
    metrics = Column(JSON)  # Additional metrics
    created_at = Column(DateTime, default=datetime.utcnow)
    
    # Relationships
    strategy = relationship("Strategy", back_populates="backtest_results")


class RiskLimit(Base):
    """Risk management configuration."""
    __tablename__ = "risk_limits"
    
    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    limit_type = Column(String(50))  # max_drawdown, daily_loss, position_size, etc.
    value = Column(Numeric(10, 4), nullable=False)
    enabled = Column(Boolean, default=True)
    created_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)


class Alert(Base):
    """Alert definitions and history."""
    __tablename__ = "alerts"
    
    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    alert_type = Column(String(50))  # price, indicator, risk, system
    condition = Column(JSON)  # Alert condition configuration
    enabled = Column(Boolean, default=True)
    triggered = Column(Boolean, default=False)
    triggered_at = Column(DateTime)
    created_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)


class RebalancingEvent(Base):
    """Portfolio rebalancing history."""
    __tablename__ = "rebalancing_events"
    
    id = Column(Integer, primary_key=True)
    trigger_type = Column(String(50))  # time, threshold, manual
    target_allocations = Column(JSON)  # Target portfolio allocations
    before_allocations = Column(JSON)  # Allocations before rebalancing
    after_allocations = Column(JSON)  # Allocations after rebalancing
    orders_placed = Column(JSON)  # Orders placed for rebalancing
    timestamp = Column(DateTime, default=datetime.utcnow, nullable=False)


class AppState(Base):
    """Application state for recovery."""
    __tablename__ = "app_state"
    
    id = Column(Integer, primary_key=True)
    key = Column(String(100), unique=True, nullable=False)
    value = Column(JSON)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)


class AuditLog(Base):
    """Security and action audit trail."""
    __tablename__ = "audit_log"
    
    id = Column(Integer, primary_key=True)
    action = Column(String(100), nullable=False)
    entity_type = Column(String(50))  # exchange, strategy, order, etc.
    entity_id = Column(Integer)
    details = Column(JSON)
    timestamp = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)


class MarketConditionsSnapshot(Base):
    """Market conditions snapshot for ML training."""
    __tablename__ = "market_conditions_snapshot"
    
    id = Column(Integer, primary_key=True)
    symbol = Column(String(20), nullable=False)
    timeframe = Column(String(10), nullable=False)
    regime = Column(String(50))  # Market regime classification
    features = Column(JSON)  # Market condition features
    strategy_name = Column(String(100))  # Strategy used at this time
    timestamp = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
    



class StrategyPerformance(Base):
    """Strategy performance records for ML training."""
    __tablename__ = "strategy_performance"
    
    id = Column(Integer, primary_key=True)
    strategy_name = Column(String(100), nullable=False, index=True)
    symbol = Column(String(20), nullable=False)
    timeframe = Column(String(10), nullable=False)
    market_regime = Column(String(50), index=True)  # Market regime when trade executed
    return_pct = Column(Numeric(10, 4))  # Return percentage
    sharpe_ratio = Column(Numeric(10, 4))  # Sharpe ratio
    win_rate = Column(Numeric(5, 2))  # Win rate (0-100)
    max_drawdown = Column(Numeric(10, 4))  # Maximum drawdown
    trade_count = Column(Integer, default=1)  # Number of trades in this period
    timestamp = Column(DateTime, default=datetime.utcnow, nullable=False, index=True)
    



class MLModelMetadata(Base):
    """ML model metadata and versions."""
    __tablename__ = "ml_model_metadata"
    
    id = Column(Integer, primary_key=True)
    model_name = Column(String(100), nullable=False)
    model_type = Column(String(50))  # classifier, regressor
    version = Column(String(20))
    file_path = Column(String(500))  # Path to saved model file
    training_metrics = Column(JSON)  # Training metrics (accuracy, MSE, etc.)
    feature_names = Column(JSON)  # List of feature names
    strategy_names = Column(JSON)  # List of strategy names
    training_samples = Column(Integer)  # Number of training samples
    trained_at = Column(DateTime, nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)
    



from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker

class Database:
    """Database connection manager."""
    
    def __init__(self):
        """Initialize database connection."""
        self.config = get_config()
        self.engine = self._create_engine()
        self.SessionLocal = async_sessionmaker(
            bind=self.engine, 
            class_=AsyncSession, 
            expire_on_commit=False
        )
        # self._create_tables() # Tables should be created via alembic or separate init script in async
    
    def _create_engine(self):
        """Create database engine."""
        db_type = self.config.get("database.type", "postgresql")
        
        if db_type == "postgresql":
            db_url = self.config.get("database.url")
            if not db_url:
                raise ValueError("PostgreSQL URL not configured")
            # Ensure URL uses async driver (e.g. postgresql+asyncpg)
            if "postgresql://" in db_url and "postgresql+asyncpg://" not in db_url:
                 # This is a naive replacement, in production we should handle this better
                 db_url = db_url.replace("postgresql://", "postgresql+asyncpg://")
            # Add connection timeout to prevent hanging
            # asyncpg connect timeout is set via connect_timeout in connect_args
            return create_async_engine(
                db_url, 
                echo=False,
                connect_args={
                    "server_settings": {"application_name": "crypto_trader"},
                    "timeout": 5,  # 5 second connection timeout
                },
                pool_pre_ping=True,  # Verify connections before using
                pool_recycle=3600,  # Recycle connections after 1 hour
                pool_timeout=5,  # Timeout when getting connection from pool
            )
        else:
            raise ValueError(f"Unsupported database type: {db_type}. Only 'postgresql' is supported.")
    
    async def create_tables(self):
        """Create all database tables."""
        async with self.engine.begin() as conn:
            await conn.run_sync(Base.metadata.create_all)
    
    def get_session(self) -> AsyncSession:
        """Get a database session."""
        return self.SessionLocal()
    
    async def close(self):
        """Close database connection."""
        await self.engine.dispose()


# Global database instance
_db_instance: Optional[Database] = None


def get_database() -> Database:
    """Get global database instance."""
    global _db_instance
    if _db_instance is None:
        _db_instance = Database()
    return _db_instance

