"""
Core Laklak functionality - Simple API for data collection
"""

from typing import Optional, Union, List
from datetime import datetime, timedelta
import logging
import sys
import os

from modules.exchanges.bybit import BybitKline
from modules.exchanges.deribit import DeribitDVOL
from modules.exchanges.yfinance import YFinanceKline
from modules.influx_writer import InfluxDBWriter

# Setup logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)


# Timeframe mapping: user-friendly to minutes
TIMEFRAME_MAP = {
    '1m': 1, '1min': 1,
    '3m': 3, '3min': 3,
    '5m': 5, '5min': 5,
    '15m': 15, '15min': 15,
    '30m': 30, '30min': 30,
    '1h': 60, '1hour': 60,
    '2h': 120, '2hour': 120,
    '4h': 240, '4hour': 240,
    '6h': 360, '6hour': 360,
    '12h': 720, '12hour': 720,
    '1d': 1440, '1day': 1440,
    '1w': 10080, '1week': 10080,
    '1M': 43200, '1month': 43200,
}

# Maximum supported periods per timeframe (based on 1000 candle limit)
# Calculated as: (1000 candles * timeframe_minutes) / (24 * 60) = max_days
MAX_PERIODS = {
    1: 0.7,      # 1min: ~17 hours (1000min / 1440)
    3: 2,        # 3min: ~2 days (3000min / 1440)
    5: 3,        # 5min: ~3.5 days (5000min / 1440)
    15: 10,      # 15min: ~10 days (15000min / 1440)
    30: 21,      # 30min: ~21 days (30000min / 1440)
    60: 42,      # 1hour: ~42 days (60000min / 1440) - API limit!
    120: 83,     # 2hour: ~83 days
    240: 167,    # 4hour: ~167 days (5.5 months)
    360: 250,    # 6hour: ~250 days (8 months)
    720: 500,    # 12hour: ~500 days (1.4 years)
    1440: 1000,  # 1day: ~1000 days (2.7 years)
    10080: 3650, # 1week: up to 10 years (generous for yfinance)
    43200: 3650, # 1month: up to 10 years (generous for yfinance)
}


class Laklak:
    """
    Simple, unified interface for collecting financial market data.
    
    Examples:
        >>> fetcher = Laklak()
        >>> fetcher.collect("BTCUSDT", exchange="bybit")
        >>> fetcher.collect(["BTCUSDT", "ETHUSDT"], exchange="bybit")
        >>> fetcher.backfill("AAPL", exchange="yfinance", days=30)
    """
    
    def __init__(self, influx_host: Optional[str] = None, 
                 influx_port: Optional[int] = None,
                 influx_db: Optional[str] = None):
        """
        Initialize Laklak with optional InfluxDB configuration.
        
        Args:
            influx_host: InfluxDB host (default from config)
            influx_port: InfluxDB port (default from config)
            influx_db: InfluxDB database name (default from config)
        """
        self.influx_host = influx_host or config.INFLUXDB_HOST
        self.influx_port = influx_port or config.INFLUXDB_PORT
        self.influx_db = influx_db or config.INFLUXDB_DATABASE
        self.writer = None
        
    def _get_writer(self) -> InfluxDBWriter:
        """Get or create InfluxDB writer instance."""
        if self.writer is None:
            self.writer = InfluxDBWriter(
                host=self.influx_host,
                port=self.influx_port,
                database=self.influx_db
            )
        return self.writer
    
    def _convert_to_influx_format(self, df, symbol, exchange, data_type):
        """Convert DataFrame to InfluxDB format."""
        data_points = []
        for _, row in df.iterrows():
            point = {
                "measurement": "market_data",
                "tags": {
                    "symbol": f"{symbol}_{exchange.upper()}",
                    "exchange": exchange,
                    "data_type": data_type
                },
                "time": row.get('timestamp', row.name),
                "fields": {}
            }
            
            # Add available fields
            for field in ['open', 'high', 'low', 'close', 'volume']:
                if field in row:
                    point["fields"][field] = float(row[field])
            
            if point["fields"]:  # Only add if we have fields
                data_points.append(point)
        
        return data_points
    
    def _parse_timeframe(self, timeframe: Union[str, int]) -> int:
        """
        Parse timeframe string to minutes.
        
        Args:
            timeframe: Timeframe as string ('1h', '5m', '1d') or int (minutes)
            
        Returns:
            int: Timeframe in minutes
        """
        if isinstance(timeframe, int):
            return timeframe
        
        timeframe_lower = timeframe.lower()
        if timeframe_lower in TIMEFRAME_MAP:
            return TIMEFRAME_MAP[timeframe_lower]
        
        raise ValueError(
            f"Invalid timeframe '{timeframe}'. "
            f"Supported: {', '.join(sorted(set(TIMEFRAME_MAP.keys())))}"
        )
    
    def _validate_period(self, timeframe_minutes: int, days: float) -> float:
        """
        Validate and adjust period based on timeframe limits.
        
        Args:
            timeframe_minutes: Timeframe in minutes
            days: Requested days
            
        Returns:
            float: Adjusted days (capped at maximum)
        """
        max_days = MAX_PERIODS.get(timeframe_minutes, 365)
        if days > max_days:
            logger.warning(
                f"Requested {days} days exceeds maximum {max_days} days for "
                f"{timeframe_minutes}min timeframe. Capping to {max_days} days."
            )
            return max_days
        return days

    def collect(self, 
                symbols: Union[str, List[str]], 
                exchange: str,
                timeframe: Union[str, int] = '1h',
                period: Union[str, int] = 30) -> bool:
        """
        Collect latest data for one or more symbols.
        
        Args:
            symbols: Single symbol or list of symbols
            exchange: Exchange name ('bybit', 'deribit', 'yfinance')
            timeframe: Time interval - string like '1m', '5m', '15m', '1h', '4h', '1d', '1w'
                      or integer in minutes (default: '1h')
            period: Data period - string like '7d', '30d', '1y' or integer days
                   (default: 30 days)
            
        Returns:
            bool: True if successful, False otherwise
            
        Note:
            Bybit API limits responses to 1000 candles. Period is automatically capped:
            - 1min: max ~17 hours
            - 5min: max ~3.5 days
            - 15min: max ~10 days
            - 1hour: max ~42 days (not 1 year!)
            - 4hour: max ~167 days (~5.5 months)
            - 1day: max ~1000 days (~2.7 years)
            
        Examples:
            >>> # 1 hour candles, last 30 days (default, within limit)
            >>> fetcher.collect("BTCUSDT", exchange="bybit")
            
            >>> # 5 minute candles, last 3 days (within limit)
            >>> fetcher.collect("BTCUSDT", exchange="bybit", timeframe="5m", period=3)
            
            >>> # 4 hour candles, last 5 months (within limit)
            >>> fetcher.collect("ETHUSDT", exchange="bybit", timeframe="4h", period=150)
            
            >>> # Daily candles, last 2 years (within limit)
            >>> fetcher.collect(["AAPL", "GOOGL"], exchange="yfinance", timeframe="1d", period=730)
            
            >>> # 15 min candles, 10 days (max for this timeframe)
            >>> fetcher.collect(["BTC-USD", "ETH-USD"], exchange="yfinance", timeframe="15m", period=10)
        """
        if isinstance(symbols, str):
            symbols = [symbols]
        
        # Parse timeframe
        resolution = self._parse_timeframe(timeframe)
        
        # Parse period (support '7d', '30d', '1y' format)
        if isinstance(period, str):
            period_lower = period.lower()
            if period_lower.endswith('d'):
                days = float(period_lower[:-1])
            elif period_lower.endswith('w'):
                days = float(period_lower[:-1]) * 7
            elif period_lower.endswith('m'):
                days = float(period_lower[:-1]) * 30
            elif period_lower.endswith('y'):
                days = float(period_lower[:-1]) * 365
            else:
                days = float(period)
        else:
            days = float(period)
        
        # Validate period for timeframe
        days = self._validate_period(resolution, days)
        
        exchange = exchange.lower()
        writer = self._get_writer()
        
        logger.info(
            f"Collecting {len(symbols)} symbols from {exchange} "
            f"[timeframe: {timeframe}, period: {days} days]"
        )
        
        success_count = 0
        for symbol in symbols:
            try:
                data = None
                if exchange == "bybit":
                    df = BybitKline.fetch_historical_kline(symbol, days, resolution)
                    if df is not None and not df.empty:
                        data = self._convert_to_influx_format(df, symbol, "bybit", "kline")
                elif exchange == "deribit":
                    base_currency = symbol.replace("USDT", "").replace("USD", "")
                    df = DeribitDVOL.fetch_historical_dvol(base_currency, days, resolution * 60)
                    if df is not None and not df.empty:
                        data = self._convert_to_influx_format(df, f"{base_currency}_DVOL", "deribit", "dvol")
                elif exchange == "yfinance":
                    df = YFinanceKline.fetch_historical_data(symbol, days, resolution)
                    if df is not None and not df.empty:
                        data = self._convert_to_influx_format(df, symbol, "yfinance", "kline")
                else:
                    logger.error(f"Unknown exchange: {exchange}")
                    continue
                
                if data and len(data) > 0:
                    writer.write_batch(data)
                    success_count += 1
                    logger.info(f"✓ Collected {len(data)} points for {symbol}")
                else:
                    logger.warning(f"✗ No data returned for {symbol}")
                    
            except Exception as e:
                logger.error(f"✗ Failed to collect {symbol}: {str(e)}")
                continue
        
        logger.info(f"Collection complete: {success_count}/{len(symbols)} successful")
        return success_count == len(symbols)
    
    def backfill(self,
                 symbols: Union[str, List[str]],
                 exchange: str,
                 timeframe: Union[str, int] = '4h',
                 period: Union[str, int] = 150) -> bool:
        """
        Backfill historical data for symbols.
        
        Args:
            symbols: Single symbol or list of symbols
            exchange: Exchange name ('bybit', 'deribit', 'yfinance')
            timeframe: Time interval like '1m', '5m', '15m', '1h', '4h', '1d' (default: '4h')
            period: How far back - string like '30d', '1y' or integer days (default: 150 days ~5 months)
            
        Returns:
            bool: True if successful, False otherwise
            
        Note:
            Default changed to 4h/150d to respect Bybit's 1000 candle limit.
            For 1 hour data, max period is ~42 days (1000 hours).
            
        Examples:
            >>> # 4 hour candles, 150 days (default, ~900 candles)
            >>> fetcher.backfill("BTCUSDT", exchange="bybit")
            
            >>> # Daily candles, 2 years (~730 candles)
            >>> fetcher.backfill("BTCUSDT", exchange="bybit", timeframe="1d", period="2y")
            
            >>> # 1 hour candles, 40 days (~960 candles, within limit)
            >>> fetcher.backfill("ETHUSDT", exchange="bybit", timeframe="1h", period=40)
            
            >>> # 4 hour candles, 6 months (within limit)
            >>> fetcher.backfill(["AAPL", "GOOGL"], exchange="yfinance", timeframe="4h", period=180)
        """
        if isinstance(symbols, str):
            symbols = [symbols]
        
        logger.info(f"Starting backfill: {len(symbols)} symbols, timeframe={timeframe}, period={period}")
        
        return self.collect(symbols, exchange, timeframe, period)


# Convenience functions for simple usage
def collect(symbol: Union[str, List[str]], 
            exchange: str,
            timeframe: Union[str, int] = '1h',
            period: Union[str, int] = 30,
            **kwargs) -> bool:
    """
    Quick function to collect data without creating a Laklak instance.
    
    Args:
        symbol: Single symbol or list of symbols
        exchange: Exchange name ('bybit', 'deribit', 'yfinance')
        timeframe: Time interval - '1m', '5m', '15m', '1h', '4h', '1d', '1w' (default: '1h')
        period: Data period - '7d', '30d', '1y' or integer days (default: 30)
    
    Examples:
        >>> from laklak import collect
        >>> 
        >>> # Simple: 1 hour candles, 30 days (default)
        >>> collect("BTCUSDT", exchange="bybit")
        >>> 
        >>> # 5 minute candles, last week
        >>> collect("BTCUSDT", exchange="bybit", timeframe="5m", period="7d")
        >>> 
        >>> # Daily candles, last year
        >>> collect(["AAPL", "GOOGL"], exchange="yfinance", timeframe="1d", period="1y")
        >>> 
        >>> # 15 min candles, 14 days
        >>> collect("ETHUSDT", exchange="bybit", timeframe="15m", period=14)
    """
    fetcher = Laklak(**kwargs)
    return fetcher.collect(symbol, exchange, timeframe, period)


def backfill(symbol: Union[str, List[str]], 
             exchange: str,
             timeframe: Union[str, int] = '4h',
             period: Union[str, int] = 150,
             **kwargs) -> bool:
    """
    Quick function to backfill data without creating a Laklak instance.
    
    Args:
        symbol: Single symbol or list of symbols
        exchange: Exchange name ('bybit', 'deribit', 'yfinance')
        timeframe: Time interval like '1h', '4h', '1d' (default: '4h')
        period: How far back - '30d', '1y' or integer days (default: 150 days)
    
    Note:
        Default is 4h/150d to respect Bybit's 1000 candle limit (~900 candles).
    
    Examples:
        >>> from laklak import backfill
        >>> 
        >>> # 4 hour candles, 150 days (default, within limit)
        >>> backfill("BTCUSDT", exchange="bybit")
        >>> 
        >>> # Daily candles, 2 years (~730 candles)
        >>> backfill("BTCUSDT", exchange="bybit", timeframe="1d", period="2y")
        >>> 
        >>> # 1 hour, 40 days (~960 candles, safe)
        >>> backfill("ETHUSDT", exchange="bybit", timeframe="1h", period=40)
    """
    fetcher = Laklak(**kwargs)
    return fetcher.backfill(symbol, exchange, timeframe, period)
