from typing import Iterable, Any, Dict, Optional
import time
import platform
import pandas as pd
from datetime import datetime
from ..core.base import Source
from ..common.logger import get_logger

logger = get_logger(__name__)


class Mt5StreamSource(Source):
    def __init__(self,
                 symbol: str = "XAUUSD",
                 period: str = "M1",
                 interval: float = 1.0,
                 limit: int = 10,
                 login: int = 0,
                 password: str = "",
                 server: str = "") -> None:
        self.symbols = [s.strip() for s in symbol.split(',')]
        self.periods = [p.strip() for p in period.split(',')]
        self.interval = interval
        self.limit = limit
        self.login = login
        self.password = password
        self.server = server
        self.platform = platform.system()
        # Key: (symbol, period) -> {time: record}
        self.last_records: Dict[tuple, Dict[Any, Dict[str, Any]]] = {}
        # Key: (symbol, period) -> latest_time
        self.last_seen_max_times: Dict[tuple, Any] = {}
        
        if self.limit < 2:
            logger.warning("Limit < 2 might miss candle completion updates. Setting limit to 2.")
            self.limit = 2
        
        if self.platform != "Windows":
             logger.warning("MT5 source usually requires Windows to run the terminal.")

    def read(self) -> Iterable[Dict[str, Any]]:
        try:
            # Delayed import to avoid hard dependency on non-Windows
            from ..common.mt5_conn import MT5Connector, MT5Config, MT5Period
        except ImportError as e:
            logger.error(f"Required dependencies for MT5 not found: {e}. Please install 'drip-etl[mt5]'.")
            raise

        config = MT5Config(
            login=self.login,
            password=self.password,
            server=self.server
        )

        try:
            with MT5Connector(config) as conn:
                # Prepare period mappings
                period_map = {}
                for p in self.periods:
                    try:
                        period_enum = getattr(MT5Period, p)
                        period_map[p] = period_enum.value
                    except AttributeError:
                        logger.warning(f"Unknown period {p}, skipping")

                if not period_map:
                    logger.error("No valid periods found. Defaulting to M1.")
                    period_map['M1'] = MT5Period.M1.value
                    self.periods = ['M1']

                logger.info(f"Starting MT5 stream for {self.symbols} {self.periods} (interval={self.interval}s)")

                while True:
                    for symbol in self.symbols:
                        for period_name in self.periods:
                            period_value = period_map.get(period_name)
                            if period_value is None:
                                continue
                                
                            key = (symbol, period_name)
                            if key not in self.last_records:
                                self.last_records[key] = {}
                            
                            try:
                                # Fetch latest data
                                df = conn.get_kline_data(symbol, period=period_value, count=self.limit)
                                
                                if df is not None and not df.empty:
                                    # Convert time to datetime if it's integer timestamp
                                    if pd.api.types.is_integer_dtype(df['time']):
                                        df['time'] = pd.to_datetime(df['time'], unit='s')
                                    
                                    records = df.to_dict('records')
                                    new_records_count = 0
                                    
                                    latest_time = df['time'].max()
                                    last_seen_max_time = self.last_seen_max_times.get(key)
                                    
                                    # Detect new period start
                                    if last_seen_max_time is not None and latest_time > last_seen_max_time:
                                        logger.info(f"New period detected for {symbol} {period_name}: {latest_time} (was {last_seen_max_time}). Finalizing previous candle.")
                                    
                                    self.last_seen_max_times[key] = latest_time

                                    for record in records:
                                        # Inject symbol and period if missing
                                        if 'symbol' not in record:
                                            record['symbol'] = symbol
                                        if 'period' not in record:
                                            record['period'] = period_name
                                        
                                        record_time = record['time']
                                        
                                        # Add is_completed flag
                                        record['is_completed'] = 1 if record_time < latest_time else 0

                                        # Check if record is new or changed
                                        should_yield = False
                                        
                                        if record_time not in self.last_records[key]:
                                            should_yield = True
                                        else:
                                            last_record = self.last_records[key][record_time]
                                            if record != last_record:
                                                should_yield = True
                                        
                                        if should_yield:
                                            self.last_records[key][record_time] = record.copy()
                                            yield record
                                            new_records_count += 1
                                    
                                    if new_records_count > 0:
                                        logger.debug(f"Yielded {new_records_count} updated records for {symbol} {period_name}")
                                        
                                    # Prune cache
                                    if len(self.last_records[key]) > self.limit * 2:
                                        sorted_times = sorted(self.last_records[key].keys())
                                        keys_to_remove = sorted_times[:-self.limit]
                                        for k in keys_to_remove:
                                            del self.last_records[key][k]
                                
                                else:
                                    logger.warning(f"No data received for {symbol} {period_name}")

                            except Exception as e:
                                logger.error(f"Error in stream loop for {symbol} {period_name}: {e}")
                    
                    time.sleep(self.interval)

        except Exception as e:
            logger.error(f"Fatal error connecting to MT5: {e}")
            raise
