#!/usr/bin/env python3
"""
Unified data source layer: LongPort (primary) + Yahoo Finance (fallback).

LongPort provides: PE, PB, EPS, BPS, dividend_yield, market_cap, realtime quotes
Yahoo Finance provides: operating margins, ROE, debt ratios, analyst data, earnings history
"""

import os
import sys
from dataclasses import dataclass, field
from typing import Optional, Literal

# Lazy imports to avoid startup cost
_longport_ctx = None
_yf = None


def _get_longport_context():
    """Get or create LongPort QuoteContext (singleton)."""
    global _longport_ctx
    if _longport_ctx is None:
        try:
            from longport import openapi
            
            # Map LONGBRIDGE_* to LONGPORT_*
            config = {}
            with open(os.path.expanduser("~/.bashrc"), "r") as f:
                for line in f:
                    if line.startswith("export LONGBRIDGE_"):
                        key, value = line.strip().split("=", 1)
                        config[key.replace("export ", "")] = value
            
            os.environ["LONGPORT_APP_KEY"] = config.get("LONGBRIDGE_APP_KEY", "")
            os.environ["LONGPORT_APP_SECRET"] = config.get("LONGBRIDGE_APP_SECRET", "")
            os.environ["LONGPORT_ACCESS_TOKEN"] = config.get("LONGBRIDGE_ACCESS_TOKEN", "")
            
            cfg = openapi.Config.from_env()
            _longport_ctx = openapi.QuoteContext(config=cfg)
        except Exception as e:
            print(f"LongPort init failed: {e}", file=sys.stderr)
    return _longport_ctx


def _get_yfinance():
    """Lazy import yfinance."""
    global _yf
    if _yf is None:
        try:
            import yfinance
            _yf = yfinance
        except ImportError:
            print("yfinance not installed, Yahoo fallback disabled", file=sys.stderr)
    return _yf


@dataclass
class UnifiedStockData:
    """Unified stock data from multiple sources."""
    
    # Identity
    symbol: str
    name: str = ""
    market: str = ""  # "US", "HK", "CN"
    currency: str = ""
    
    # Price data (LongPort primary)
    last_price: Optional[float] = None
    prev_close: Optional[float] = None
    open_price: Optional[float] = None
    high: Optional[float] = None
    low: Optional[float] = None
    volume: Optional[int] = None
    turnover: Optional[float] = None
    
    # Valuation metrics (LongPort primary)
    pe_ttm: Optional[float] = None
    pb: Optional[float] = None
    dividend_yield: Optional[float] = None
    eps_ttm: Optional[float] = None
    bps: Optional[float] = None  # Book value per share
    market_cap: Optional[float] = None
    
    # Trading metrics (LongPort only)
    turnover_rate: Optional[float] = None
    volume_ratio: Optional[float] = None
    change_rate: Optional[float] = None
    
    # Share info (LongPort)
    total_shares: Optional[int] = None
    circulating_shares: Optional[int] = None
    
    # Fundamental extras (Yahoo Finance fallback)
    operating_margin: Optional[float] = None
    profit_margin: Optional[float] = None
    roe: Optional[float] = None
    roa: Optional[float] = None
    revenue_growth: Optional[float] = None
    earnings_growth: Optional[float] = None
    debt_to_equity: Optional[float] = None
    current_ratio: Optional[float] = None
    
    # Analyst data (Yahoo Finance only)
    recommendation: Optional[str] = None
    target_price: Optional[float] = None
    num_analysts: Optional[int] = None
    
    # Earnings (Yahoo Finance only)
    earnings_history: Optional[object] = None  # DataFrame
    
    # Price history (Yahoo Finance primary, LongPort fallback)
    price_history: Optional[object] = None  # DataFrame
    
    # Metadata
    data_sources: list = field(default_factory=list)  # ["longport", "yahoo"]
    
    
def _detect_market(symbol: str) -> tuple[str, str]:
    """Detect market from symbol format.
    
    Returns: (normalized_symbol, market)
    Examples:
        "O.US" -> ("O.US", "US")
        "AAPL" -> ("AAPL.US", "US")
        "823.HK" -> ("823.HK", "HK")
        "000001.SZ" -> ("000001.SZ", "CN")
    """
    symbol = symbol.upper().strip()
    
    if symbol.endswith(".US"):
        return symbol, "US"
    elif symbol.endswith(".HK"):
        return symbol, "HK"
    elif symbol.endswith(".SZ") or symbol.endswith(".SH"):
        return symbol, "CN"
    elif symbol.endswith("-USD"):
        return symbol, "CRYPTO"
    else:
        # Assume US stock if no suffix
        return f"{symbol}.US", "US"


def fetch_from_longport(symbol: str, verbose: bool = False) -> Optional[UnifiedStockData]:
    """Fetch data from LongPort SDK."""
    ctx = _get_longport_context()
    if ctx is None:
        return None
    
    try:
        from longport.openapi import CalcIndex
        
        normalized, market = _detect_market(symbol)
        
        if verbose:
            print(f"  [LongPort] Fetching {normalized}...", file=sys.stderr)
        
        # Get quote
        quotes = ctx.quote([normalized])
        if not quotes:
            return None
        q = quotes[0]
        
        # Get static info (EPS, BPS, shares)
        static_infos = ctx.static_info([normalized])
        static = static_infos[0] if static_infos else None
        
        # Get calc indexes (PE, PB, dividend yield, market cap)
        indexes = [
            CalcIndex.PeTtmRatio,
            CalcIndex.PbRatio,
            CalcIndex.DividendRatioTtm,
            CalcIndex.TotalMarketValue,
            CalcIndex.TurnoverRate,
            CalcIndex.VolumeRatio,
            CalcIndex.ChangeRate,
        ]
        calc_results = ctx.calc_indexes([normalized], indexes)
        calc = calc_results[0] if calc_results else None
        
        # Build result
        data = UnifiedStockData(
            symbol=normalized,
            name=getattr(static, 'name_en', '') or getattr(static, 'name_cn', ''),
            market=market,
            currency=getattr(static, 'currency', ''),
            # Price
            last_price=q.last_done,
            prev_close=q.prev_close,
            open_price=q.open,
            high=q.high,
            low=q.low,
            volume=q.volume,
            turnover=q.turnover,
            # Valuation
            pe_ttm=getattr(calc, 'pe_ttm_ratio', None),
            pb=getattr(calc, 'pb_ratio', None),
            dividend_yield=getattr(calc, 'dividend_ratio_ttm', None),
            eps_ttm=getattr(static, 'eps_ttm', None),
            bps=getattr(static, 'bps', None),
            market_cap=getattr(calc, 'total_market_value', None),
            # Trading
            turnover_rate=getattr(calc, 'turnover_rate', None),
            volume_ratio=getattr(calc, 'volume_ratio', None),
            change_rate=getattr(calc, 'change_rate', None),
            # Shares
            total_shares=getattr(static, 'total_shares', None),
            circulating_shares=getattr(static, 'circulating_shares', None),
            # Source
            data_sources=["longport"],
        )
        
        return data
        
    except Exception as e:
        if verbose:
            print(f"  [LongPort] Error: {e}", file=sys.stderr)
        return None


def fetch_from_yahoo(symbol: str, verbose: bool = False) -> Optional[UnifiedStockData]:
    """Fetch data from Yahoo Finance (fallback for missing fields)."""
    yf = _get_yfinance()
    if yf is None:
        return None
    
    try:
        # Convert symbol format for Yahoo
        yahoo_symbol = symbol.replace(".US", "")
        if symbol.endswith(".HK"):
            yahoo_symbol = symbol  # Yahoo uses .HK suffix
        
        if verbose:
            print(f"  [Yahoo] Fetching {yahoo_symbol}...", file=sys.stderr)
        
        stock = yf.Ticker(yahoo_symbol)
        info = stock.info
        
        if not info:
            return None
        
        # Get price history
        try:
            price_history = stock.history(period="1y")
        except Exception:
            price_history = None
        
        # Get earnings history
        try:
            earnings_history = stock.earnings_dates
        except Exception:
            earnings_history = None
        
        # Build result with Yahoo data
        data = UnifiedStockData(
            symbol=symbol,
            name=info.get("shortName", "") or info.get("longName", ""),
            market="US" if not symbol.endswith(".HK") else "HK",
            currency=info.get("currency", ""),
            # Price
            last_price=info.get("regularMarketPrice") or info.get("currentPrice"),
            prev_close=info.get("regularMarketPreviousClose"),
            open_price=info.get("regularMarketOpen"),
            high=info.get("regularMarketDayHigh"),
            low=info.get("regularMarketDayLow"),
            volume=info.get("regularMarketVolume"),
            # Valuation
            pe_ttm=info.get("trailingPE"),
            pb=info.get("priceToBook"),
            dividend_yield=info.get("dividendYield"),
            eps_ttm=info.get("trailingEps"),
            market_cap=info.get("marketCap"),
            # Fundamentals (Yahoo extras)
            operating_margin=info.get("operatingMargins"),
            profit_margin=info.get("profitMargins"),
            roe=info.get("returnOnEquity"),
            roa=info.get("returnOnAssets"),
            revenue_growth=info.get("revenueGrowth"),
            earnings_growth=info.get("earningsGrowth"),
            debt_to_equity=info.get("debtToEquity"),
            current_ratio=info.get("currentRatio"),
            # Analyst
            recommendation=info.get("recommendationKey"),
            target_price=info.get("targetMeanPrice"),
            num_analysts=info.get("numberOfAnalystOpinions"),
            # History
            earnings_history=earnings_history,
            price_history=price_history,
            # Source
            data_sources=["yahoo"],
        )
        
        return data
        
    except Exception as e:
        if verbose:
            print(f"  [Yahoo] Error: {e}", file=sys.stderr)
        return None


def merge_data(longport_data: Optional[UnifiedStockData], 
               yahoo_data: Optional[UnifiedStockData]) -> Optional[UnifiedStockData]:
    """Merge data from both sources, LongPort takes priority."""
    if longport_data is None and yahoo_data is None:
        return None
    
    if longport_data is None:
        return yahoo_data
    
    if yahoo_data is None:
        return longport_data
    
    # Merge: LongPort primary, Yahoo fills gaps
    merged = longport_data
    
    # Fill Yahoo extras
    for field in ['operating_margin', 'profit_margin', 'roe', 'roa', 
                  'revenue_growth', 'earnings_growth', 'debt_to_equity', 
                  'current_ratio', 'recommendation', 'target_price', 
                  'num_analysts', 'earnings_history', 'price_history']:
        if getattr(merged, field) is None:
            val = getattr(yahoo_data, field)
            if val is not None:
                setattr(merged, field, val)
    
    # Track sources
    merged.data_sources = ["longport", "yahoo"]
    
    return merged


def fetch_stock_data_unified(symbol: str, verbose: bool = False) -> Optional[UnifiedStockData]:
    """Main entry point: fetch stock data with LongPort primary, Yahoo fallback.
    
    Args:
        symbol: Stock symbol (e.g., "O", "O.US", "823.HK", "AAPL")
        verbose: Print debug info
        
    Returns:
        UnifiedStockData or None
    """
    normalized, market = _detect_market(symbol)
    
    if verbose:
        print(f"\nFetching {normalized} ({market})...", file=sys.stderr)
    
    # 1. Try LongPort first (for all markets)
    longport_data = fetch_from_longport(normalized, verbose)
    
    # 2. For US stocks, also try Yahoo for extras
    yahoo_data = None
    if market == "US":
        yahoo_data = fetch_from_yahoo(normalized, verbose)
    
    # 3. Merge results
    result = merge_data(longport_data, yahoo_data)
    
    if result and verbose:
        print(f"  Sources: {result.data_sources}", file=sys.stderr)
        print(f"  PE: {result.pe_ttm}, PB: {result.pb}, Yield: {result.dividend_yield}%", file=sys.stderr)
    
    return result


# Convenience function for backward compatibility
def fetch_stock_data(symbol: str, verbose: bool = False):
    """Backward compatible wrapper."""
    return fetch_stock_data_unified(symbol, verbose)


if __name__ == "__main__":
    # Test
    symbols = ["O", "823.HK", "MAIN"]
    for sym in symbols:
        data = fetch_stock_data_unified(sym, verbose=True)
        if data:
            print(f"\n{data.symbol}:")
            print(f"  Name: {data.name}")
            print(f"  Price: {data.last_price} {data.currency}")
            print(f"  PE: {data.pe_ttm}, PB: {data.pb}")
            print(f"  Dividend Yield: {data.dividend_yield}%")
            print(f"  EPS: {data.eps_ttm}, BPS: {data.bps}")
            print(f"  Market Cap: {data.market_cap:,.0f}" if data.market_cap else "")
            print(f"  Sources: {data.data_sources}")
