# source: https://raw.githubusercontent.com/GithubVetintegrations/Crypto-bot/2ba75d7b3377fd786ea37a074c35d2346c568582/freqtrade/user_data/strategies/NewsAdaptiveStrategy.py
import logging
from datetime import datetime, timezone
from typing import Dict, List, Optional, Tuple, Union
import requests
import json

import numpy as np
import pandas as pd
import talib.abstract as ta
from pandas import DataFrame
from pydantic import BaseModel

from freqtrade.strategy import IStrategy, merge_informative_pair
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
                                IntParameter, RealParameter, timeframe_to_minutes)

logger = logging.getLogger(__name__)


class SignalData(BaseModel):
    """Signal data structure from orchestrator"""
    pair: str
    signal_strength: float
    signal_type: str  # 'buy', 'sell', 'hold'
    confidence: float
    sources: List[str]
    timestamp: datetime
    metadata: Dict

class SentimentFeatures(BaseModel):
    """Sentiment features from NLP worker"""
    sentiment_score: float
    sentiment_model: str  # 'finbert' or 'vader'
    topic_classification: str
    topic_confidence: float
    entity_count: int
    relevance_score: float


class Github_GithubVetintegrations_Crypto_bot__NewsAdaptiveStrategy__20250928_151138(IStrategy):
    """
    Github_GithubVetintegrations_Crypto_bot__NewsAdaptiveStrategy__20250928_151138 combines technical analysis with external signals
    from the signal orchestrator to make trading decisions.
    """
    
    INTERFACE_VERSION = 3
    
    # Strategy parameters
    timeframe = '5m'
    
    # ROI table
    minimal_roi = {
        "60": 0.01,
        "30": 0.02,
        "0": 0.04
    }
    
    # Stoploss
    stoploss = -0.10
    
    # Trailing stop
    trailing_stop = False
    trailing_stop_positive = 0.01
    trailing_stop_positive_offset = 0.02
    trailing_only_offset_is_reached = False
    
    # Hyperopt parameters
    ema_short = IntParameter(5, 20, default=12, space="buy")
    ema_long = IntParameter(20, 50, default=26, space="buy")
    signal_threshold = DecimalParameter(0.1, 0.9, default=0.6, space="buy")
    rsi_buy = IntParameter(20, 40, default=30, space="buy")
    rsi_sell = IntParameter(60, 80, default=70, space="sell")
    
    # External signal integration
    external_signal_weight = DecimalParameter(0.1, 0.8, default=0.4, space="buy")
    min_confidence = DecimalParameter(0.3, 0.9, default=0.6, space="buy")
    
    def informative_pairs(self):
        """
        Define additional, informative pair/interval combinations to be cached from the exchange.
        These pairs will automatically be available for use in populate_indicators.
        """
        pairs = self.dp.current_whitelist()
        informative_pairs = []
        
        # Add 1h and 4h timeframes for better trend analysis
        for pair in pairs:
            informative_pairs.append((pair, '1h'))
            informative_pairs.append((pair, '4h'))
            
        return informative_pairs
    
    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Adds several different TA indicators to the given DataFrame
        """
        # Technical indicators
        dataframe['ema_short'] = ta.EMA(dataframe, timeperiod=self.ema_short.value)
        dataframe['ema_long'] = ta.EMA(dataframe, timeperiod=self.ema_long.value)
        dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
        dataframe['macd'], dataframe['macdsignal'], dataframe['macdhist'] = ta.MACD(dataframe)
        dataframe['bb_upper'], dataframe['bb_middle'], dataframe['bb_lower'] = ta.BBANDS(dataframe)
        dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
        
        # Volume indicators
        dataframe['volume_sma'] = dataframe['volume'].rolling(window=20).mean()
        dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_sma']
        
        # Bollinger Band position
        dataframe['bb_position'] = (dataframe['close'] - dataframe['bb_lower']) / (dataframe['bb_upper'] - dataframe['bb_lower'])
        
        # EMA crossover signals
        dataframe['ema_cross'] = np.where(
            (dataframe['ema_short'] > dataframe['ema_long']) & 
            (dataframe['ema_short'].shift(1) <= dataframe['ema_long'].shift(1)), 1, 0
        )
        
        # Add higher timeframe data
        for timeframe in ['1h', '4h']:
            informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=timeframe)
            informative = self.populate_indicators_informative(informative, timeframe)
            dataframe = merge_informative_pair(dataframe, informative, self.timeframe, timeframe, ffill=True)
        
        return dataframe
    
    def populate_indicators_informative(self, dataframe: DataFrame, timeframe: str) -> DataFrame:
        """Add indicators for informative timeframes"""
        dataframe[f'ema_short_{timeframe}'] = ta.EMA(dataframe, timeperiod=12)
        dataframe[f'ema_long_{timeframe}'] = ta.EMA(dataframe, timeperiod=26)
        dataframe[f'rsi_{timeframe}'] = ta.RSI(dataframe, timeperiod=14)
        
        return dataframe
    
    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Based on TA indicators, populates the entry signal for the given dataframe
        """
        # Technical conditions
        conditions = [
            # EMA crossover
            (dataframe['ema_short'] > dataframe['ema_long']),
            # RSI not overbought
            (dataframe['rsi'] < self.rsi_sell.value),
            # Price above lower Bollinger Band
            (dataframe['close'] > dataframe['bb_lower']),
            # Volume confirmation
            (dataframe['volume_ratio'] > 1.2),
            # Higher timeframe trend alignment
            (dataframe['ema_short_1h'] > dataframe['ema_long_1h']),
        ]
        
        # External signal integration
        external_signal = self.get_external_signal(metadata['pair'])
        if external_signal and external_signal.signal_type == 'buy':
            signal_condition = (
                (dataframe['close'] > 0) &  # Always true, just for structure
                (external_signal.confidence >= self.min_confidence.value)
            )
            conditions.append(signal_condition)
        
        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x & y, conditions),
                'enter_long'] = 1
        
        return dataframe
    
    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        """
        Based on TA indicators, populates the exit signal for the given dataframe
        """
        # Technical conditions
        conditions = [
            # EMA crossover
            (dataframe['ema_short'] < dataframe['ema_long']),
            # RSI overbought
            (dataframe['rsi'] > self.rsi_sell.value),
            # Price near upper Bollinger Band
            (dataframe['bb_position'] > 0.8),
        ]
        
        # External signal integration
        external_signal = self.get_external_signal(metadata['pair'])
        if external_signal and external_signal.signal_type == 'sell':
            signal_condition = (
                (dataframe['close'] > 0) &  # Always true, just for structure
                (external_signal.confidence >= self.min_confidence.value)
            )
            conditions.append(signal_condition)
        
        if conditions:
            dataframe.loc[
                reduce(lambda x, y: x & y, conditions),
                'exit_long'] = 1
        
        return dataframe
    
    def get_external_signal(self, pair: str) -> Optional[SignalData]:
        """
        Fetch external signal from the policy engine
        
        Inspired by signal consumption patterns from risabhmishra/algotrading-sentimentanalysis-genai
        """
        try:
            import requests
            import os
            
            # Try policy engine first (new endpoint)
            policy_engine_url = os.getenv('POLICY_ENGINE_URL', 'http://policy-engine:8000')
            response = requests.get(f"{policy_engine_url}/signal", params={'pair': pair}, timeout=5)
            
            if response.status_code == 200:
                data = response.json()
                # Convert policy engine response to SignalData format
                return SignalData(
                    pair=pair,
                    signal_strength=data['strength'],
                    signal_type=data['side'],
                    confidence=data['confidence'],
                    timestamp=datetime.now(timezone.utc),
                    metadata={
                        'ttl': data['ttl'],
                        'explain': data['explain'],
                        'source': 'policy_engine'
                    }
                )
            
            # Fallback to signal orchestrator (legacy)
            orchestrator_url = os.getenv('SIGNAL_ORCHESTRATOR_URL', 'http://signal-orchestrator:8000')
            response = requests.get(f"{orchestrator_url}/signal/{pair}", timeout=5)
            
            if response.status_code == 200:
                data = response.json()
                if data.get('signal'):
                    signal_data = data['signal']
                    return SignalData(
                        pair=pair,
                        signal_strength=signal_data['strength'],
                        signal_type=signal_data['signal_type'],
                        confidence=signal_data['confidence'],
                        timestamp=datetime.fromisoformat(signal_data['timestamp'].replace('Z', '+00:00')),
                        metadata=signal_data.get('metadata', {})
                    )
                    
        except Exception as e:
            logger.warning(f"Failed to fetch external signal for {pair}: {e}")
        
        return None
    
    def get_sentiment_features(self, pair: str) -> Optional[SentimentFeatures]:
        """
        Fetch sentiment features from NLP worker
        
        Returns sentiment scores, topic classification, and entity information
        """
        try:
            import requests
            import os
            
            nlp_worker_url = os.getenv('NLP_WORKER_URL', 'http://nlp-worker:8002')
            
            # Get recent events for the pair (mock for now)
            # In real implementation, this would fetch from event stream
            sample_text = f"{pair} showing strong bullish momentum with institutional adoption"
            
            # Get sentiment score
            sentiment_response = requests.post(
                f"{nlp_worker_url}/score",
                json={"text": sample_text},
                timeout=5
            )
            
            if sentiment_response.status_code != 200:
                return None
            
            sentiment_data = sentiment_response.json()
            
            # Get topic classification
            topic_response = requests.post(
                f"{nlp_worker_url}/topic",
                json={"text": sample_text},
                timeout=5
            )
            
            topic_data = topic_response.json() if topic_response.status_code == 200 else {
                "topic_name": "General",
                "confidence": 0.5
            }
            
            # Get entity extraction
            ner_response = requests.post(
                f"{nlp_worker_url}/ner",
                json={"text": sample_text},
                timeout=5
            )
            
            ner_data = ner_response.json() if ner_response.status_code == 200 else {
                "entities": [],
                "crypto_entities": []
            }
            
            return SentimentFeatures(
                sentiment_score=sentiment_data['score'],
                sentiment_model=sentiment_data['model'],
                topic_classification=topic_data['topic_name'],
                topic_confidence=topic_data['confidence'],
                entity_count=len(ner_data['entities']) + len(ner_data['crypto_entities']),
                relevance_score=0.8  # Mock relevance score
            )
            
        except Exception as e:
            logger.warning(f"Failed to fetch sentiment features for {pair}: {e}")
            return None
    
    def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
                       current_rate: float, current_profit: float, **kwargs) -> float:
        """
        Custom stoploss logic using ATR
        """
        dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        last_candle = dataframe.iloc[-1].squeeze()
        
        # Use ATR for dynamic stoploss
        atr_multiplier = 2.0
        atr_stoploss = last_candle['atr'] * atr_multiplier / current_rate
        
        # Combine with fixed stoploss
        return max(self.stoploss, -atr_stoploss)
    
    def custom_exit(self, pair: str, trade: 'Trade', current_time: datetime, current_rate: float,
                   current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
        """
        Custom exit logic based on external signals
        """
        external_signal = self.get_external_signal(pair)
        
        if external_signal:
            # Exit on strong sell signal
            if (external_signal.signal_type == 'sell' and 
                external_signal.confidence >= 0.8 and
                external_signal.signal_strength <= -0.7):
                return 'external_sell_signal'
            
            # Exit on negative news
            if ('news' in external_signal.sources and 
                external_signal.signal_strength <= -0.6):
                return 'negative_news'
        
        return None
    
    def leverage(self, pair: str, current_time: datetime, current_rate: float,
                proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], 
                side: str, **kwargs) -> float:
        """
        Customize leverage for each new trade
        """
        return 1.0  # Spot trading only
