from datetime import datetime
import time
import pandas as pd
from monitor_min import Monitor
from config import *

class CommonCoinMonitor(Monitor):
    """코인 모니터링 공통 로직 클래스"""

    def __init__(self, cooldown_file: str = 'coins_buy_time.json') -> None:
        super().__init__(cooldown_file)

    def get_balances(self) -> dict:
        """현재 보유 잔고 정보를 가져옵니다."""
        tmps = self.getBalances()
        balances = {}
        for tmp in tmps:
            balances[tmp['currency']] = {
                'balance': float(tmp['balance']), 
                'avg_buy_price': float(tmp['avg_buy_price'])
            }
        return balances

    def check_cooldown(self, symbol: str, side: str = 'buy') -> bool:
        """매수/매도 쿨다운 시간을 확인합니다."""
        current_time = datetime.now()
        last_trade_dt = self.buy_cooldown.get(symbol, {}).get(side, {}).get('datetime')
        
        if last_trade_dt:
            time_diff = current_time - last_trade_dt
            if time_diff.total_seconds() < BUY_MINUTE_LIMIT:
                print(f"{symbol}: {side} 금지 중 (남은 시간: {BUY_MINUTE_LIMIT - time_diff.total_seconds():.0f}초)")
                return False
        return True

    def save_trade_record(self, symbol: str, side: str, signal: str) -> None:
        """거래 기록을 저장합니다."""
        if self.cooldown_file is not None:
            try:
                self.last_signal[symbol] = signal
            except Exception:
                self.last_signal[symbol] = ''
            self.buy_cooldown.setdefault(symbol, {})[side] = {
                'datetime': datetime.now(), 
                'signal': signal
            }
            self._save_buy_cooldown()

    def check_5_week_lowest(self, data: pd.DataFrame) -> bool:
        """5주봉이 20주봉이나 40주봉보다 아래에 있는지 체크합니다."""
        try:
            hours_in_week = 24 * 7  # 168 hours
            period_5w = 5 * hours_in_week  # 840 hours
            period_20w = 20 * hours_in_week  # 3,360 hours
            period_40w = 40 * hours_in_week  # 6,720 hours

            if len(data) >= period_40w:
                wma5 = data['Close'].rolling(window=period_5w).mean().iloc[-1]
                wma20 = data['Close'].rolling(window=period_20w).mean().iloc[-1]
                wma40 = data['Close'].rolling(window=period_40w).mean().iloc[-1]

                # 5-week MA is the lowest among 5, 20, 40 week MAs
                if (wma5 < wma20) and (wma5 < wma40):
                    return True
        except Exception:
            pass
        return False

    def execute_buy(self, symbol: str, buy_amount: float, signal: str, current_price: float) -> bool:
        """매수를 실행합니다."""
        try:
            actual_buy_amount = self.hts.buyCoinMarket(symbol, buy_amount)
            self.save_trade_record(symbol, 'buy', signal)
            
            print(f"{KR_COINS[symbol]} ({symbol}) [{signal}], 현재가: {current_price:.4f}, {int(BUY_MINUTE_LIMIT/60)}분간 매수 금지 시작")
            self.sendMsg("{}".format(self.format_message(symbol, KR_COINS[symbol], current_price, signal, actual_buy_amount)))
            return True
        except Exception as e:
            print(f"Error buying {symbol}: {str(e)}")
            return False

    def execute_sell(self, symbol: str, sell_amount: float, signal: str, current_price: float, balances: dict) -> bool:
        """매도를 실행합니다."""
        try:
            available_balance = 0
            if balances and symbol in balances:
                available_balance = float(balances[symbol].get('balance', 0))
            
            if available_balance <= 0:
                return False
                
            actual_sell_amount = available_balance * sell_amount
            _ = self.hts.sellCoinMarket(symbol, 0, actual_sell_amount)
            self.save_trade_record(symbol, 'sell', signal)
            
            print(f"{KR_COINS[symbol]} ({symbol}) [{signal} 매도], 현재가: {current_price:.4f}")
            self.sendMsg("[KRW-COIN]\n" + f"• 매도 [COIN] {KR_COINS[symbol]} ({symbol}): {signal} ({'₩'}{current_price:.4f})")
            return True
        except Exception as e:
            print(f"Error selling {symbol}: {str(e)}")
            return False

    def process_inverse_data(self, symbol: str, interval: int, data: pd.DataFrame, balances: dict, coin_strategy) -> bool:
        """인버스 데이터를 처리하여 매도 신호를 확인합니다."""
        try:
            inverse_data = self.inverse_data(data)
            recent_inverse_data = self.annotate_signals(symbol, interval, inverse_data)
            
            # 허용된 인버스 매도 신호만 처리 (시간봉별 신호 포함)
            last_signal = str(recent_inverse_data['signal'].iloc[-1]) if 'signal' in recent_inverse_data.columns else ''
            allowed_signals = coin_strategy.get_sell_signals()
            
            if last_signal not in allowed_signals:
                return False
                
            if not self.check_cooldown(symbol, 'sell'):
                return False
            
            # 코인별 전략에 따라 매도 비율 결정
            sell_ratio = coin_strategy.get_sell_amount_ratio(last_signal)
                
            return self.execute_sell(symbol, sell_ratio, last_signal, recent_inverse_data['Close'].iloc[-1], balances)
        except Exception as e:
            print(f"Error processing inverse data for {symbol}: {str(e)}")
            return False

    def process_normal_data(self, symbol: str, interval: int, data: pd.DataFrame, coin_strategy) -> bool:
        """일반 데이터를 처리하여 매수 신호를 확인합니다."""
        try:
            data = self.calculate_technical_indicators(data)
            recent_data = self.annotate_signals(symbol, interval, data)
            
            # XRP 전용 신호 확인 (hasattr로 메서드 존재 여부 확인)
            if hasattr(coin_strategy, 'check_xrp_specific_signals'):
                xrp_signal, xrp_point = coin_strategy.check_xrp_specific_signals(recent_data, len(recent_data) - 1)
                if xrp_point == 1:
                    recent_data.at[recent_data.index[-1], 'signal'] = xrp_signal
                    recent_data.at[recent_data.index[-1], 'point'] = xrp_point
            
            if recent_data['point'].iloc[-1] != 1:
                return False
                
            if not self.check_cooldown(symbol, 'buy'):
                return False
            
            # 코인별 전략에 따라 매수 금액 결정
            signal = recent_data['signal'].iloc[-1]
            current_price = recent_data['Close'].iloc[-1]
            check_5_week_lowest = self.check_5_week_lowest(data)
            
            buy_amount = coin_strategy.get_buy_amount(signal, current_price, check_5_week_lowest)
            
            return self.execute_buy(symbol, buy_amount, signal, current_price)
        except Exception as e:
            print(f"Error processing normal data for {symbol}: {str(e)}")
            return False

    def monitor_single_coin(self, symbol: str, coin_strategy) -> None:
        """단일 코인을 모니터링합니다."""
        print("[{}] {}".format(datetime.now().strftime('%Y-%m-%d %H:%M:%S'), symbol))
        
        # 3분봉과 1일봉으로 초기 신호 확인
        intervals = [3, 1440]
        data = self.get_coin_some_data(symbol, intervals)

        for interval in intervals:

            data[interval] = self.calculate_technical_indicators(data[interval])
            recent_data = self.annotate_signals(symbol, interval, data[interval])

            # 매수라면
            if recent_data['point'].iloc[-1] == 1:

                balances = self.get_balances()

                # 인버스 데이터 처리 (매도)
                self.process_inverse_data(symbol, interval, data[interval], balances, coin_strategy)

                # 일반 데이터 처리 (매수)
                self.process_normal_data(symbol, interval, data[interval], coin_strategy)
            else:
                print(f"Data for {symbol} is empty or None.")

        time.sleep(1)
