# source: https://raw.githubusercontent.com/saucisson1sauvage/trading-evolution-system/4e530eb8b93637bb812996c0e820f027217f982b/user_data/strategies/GPTreeStrategy.py
import json
import logging
import sys
import os
from pathlib import Path
from typing import Dict, Any, Union
import pandas as pd
from pandas import DataFrame
from freqtrade.strategy import IStrategy

# Standard Pathing
try:
    from gp_blocks import *
except ImportError:
    sys.path.append(str(Path(__file__).parent))
    from gp_blocks import *

class Github_saucisson1sauvage_trading_evolution_system__GPTreeStrategy__20260309_233138(IStrategy):
    INTERFACE_VERSION = 3
    timeframe = '5m'
    process_only_new_candles = False
    # DISABLING ROI AND STOPLOSS TO UNBLOCK EVOLUTION
    minimal_roi = {"0": 100} 
    stoploss = -0.99
    startup_candle_count = 30

    def __init__(self, config: dict) -> None:
        super().__init__(config)
        self.logger = logging.getLogger(__name__)
        self.genome_path = Path(self.config['user_data_dir']) / "current_genome.json"
        self.genome: Dict[str, Any] = {}
        self._load_genome()

    def _load_genome(self) -> None:
        if not self.genome_path.exists():
            self.genome_path = Path(self.config['user_data_dir']) / "current_genome.json"
        with self.genome_path.open('r', encoding='utf-8') as f:
            self.genome = json.load(f)

    def evaluate_node(self, node: Dict[str, Any], dataframe: pd.DataFrame) -> pd.Series:
        if not isinstance(node, dict):
            return pd.Series(False, index=dataframe.index)

        if "constant" in node:
            try:
                # Ensure constant is a float, catching LLM strings
                val = float(node["constant"])
                return pd.Series(val, index=dataframe.index)
            except (ValueError, TypeError):
                return pd.Series(0.0, index=dataframe.index)
        
        if "primitive" in node:
            name = node["primitive"]
            params = node.get("parameters", {})
            
            # Numeric & Bool Helper Blocks
            if name in BLOCK_REGISTRY.get('num', {}) or name in BLOCK_REGISTRY.get('bool_helper', {}):
                func = BLOCK_REGISTRY.get('num', {}).get(name) or BLOCK_REGISTRY.get('bool_helper', {}).get(name)
                return func(dataframe, **params)
            
            # Comparator Blocks
            if name in BLOCK_REGISTRY.get('comparator', {}):
                l = self.evaluate_node(node.get("left", {}), dataframe)
                r = self.evaluate_node(node.get("right", {}), dataframe)
                return BLOCK_REGISTRY['comparator'][name](l, r)
        
        if "operator" in node:
            op = node["operator"].upper()
            c = node.get("children", [])
            
            if op in BLOCK_REGISTRY.get('operator', {}):
                # Dynamically evaluate children and pass them to the operator
                c_evals = [self.evaluate_node(child, dataframe) for child in c]
                # Fallback for empty/single child depending on operator requirements
                if op == "AND" and len(c_evals) >= 2:
                    return BLOCK_REGISTRY['operator'][op](c_evals[0], c_evals[1])
                elif op == "OR":
                    if len(c_evals) >= 2:
                        return BLOCK_REGISTRY['operator'][op](c_evals[0], c_evals[1])
                    elif len(c_evals) == 1:
                        return c_evals[0]
                elif op == "NOT" and len(c_evals) >= 1:
                    return BLOCK_REGISTRY['operator'][op](c_evals[0])
            
        return pd.Series(False, index=dataframe.index)

    def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        # Pre-calculate RSI to ensure it's available for the evaluation
        dataframe['rsi'] = get_rsi(dataframe, window=14)
        return dataframe

    def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
        try:
            # Force signals to be boolean then int
            res = self.evaluate_node(self.genome.get("entry_tree", {}), dataframe)
            dataframe['enter_long'] = res.fillna(False).astype(int)
        except Exception as e:
            log_path = Path(self.config['user_data_dir']) / "logs" / "strategy_debug.log"
            with open(log_path, "a") as f:
                f.write(f"ENTRY ERROR: {e}\n")
            dataframe['enter_long'] = 0

        # Smoke Test Injection: Forced buy on first candle if flag is set
        if os.environ.get('FREQTRADE_SMOKE_TEST') == '1':
            if len(dataframe) > 0:
                dataframe.loc[dataframe.index[0], 'enter_long'] = 1
                dataframe.loc[dataframe.index[0], 'enter_tag'] = 'smoke_test_injection'

        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        try:
            res = self.evaluate_node(self.genome.get("exit_tree", {}), dataframe)
            dataframe['exit_long'] = res.fillna(False).astype(int)
        except Exception as e:
            log_path = Path(self.config['user_data_dir']) / "logs" / "strategy_debug.log"
            with open(log_path, "a") as f:
                f.write(f"EXIT ERROR: {e}\n")
            dataframe['exit_long'] = 0
        return dataframe
