"""LEGACY DISABLED ENTRYPOINT.

This monolithic AutoTrader is retained only for historical reference and must
not be used as the production/live core. It can exit early unless
CTB_ALLOW_LEGACY_AUTOTRADER=true is set explicitly. The supported target stack
is the modular `src/` architecture documented in README.md.
"""

import requests
import json
import os
import time
import threading
import websocket
import traceback
import ssl
from datetime import datetime
from dotenv import load_dotenv
import ccxt
import urllib3

from alerting import build_risk_alert, build_trade_alert
from config import BotConfig, RuntimePaths
from daily_metrics import calculate_daily_metrics
from execution import TradingExecutor
from journal import append_journal_event
from market_data_cache import BaselinePriceCache, should_log_error
from market_universe import select_scan_coins
from near_miss import append_near_miss, build_near_miss_event, leverage_for_coin
from paper_trading import PaperExchange
from position_sizing import calculate_position_size
from restart_state import recover_position_state
from risk import risk_gate
from strategy import PositionState, exit_long_position, should_enter_by_strategy_family
from telegram_alerts import TelegramAlertConfig, send_telegram_alert

LEGACY_AUTOTRADER_DISABLED_MESSAGE = "Legacy AutoTrader disabled; use src/tools/preflight.py and v76 executors"


def require_legacy_autotrader_enabled():
    if os.getenv("CTB_ALLOW_LEGACY_AUTOTRADER", "").lower() != "true":
        print(LEGACY_AUTOTRADER_DISABLED_MESSAGE)
        raise SystemExit(2)


CONFIG_FILE = "strategy_config.json"
CONFIG = BotConfig.from_file(CONFIG_FILE)
STRATEGY_ID = os.getenv("CTB_STRATEGY_ID", "v61_tight_survival")
PATHS = RuntimePaths.from_config(CONFIG)
PATHS.ensure_dirs()
EXECUTOR = None
TELEGRAM_CONFIG = TelegramAlertConfig.from_env()

if not CONFIG.tls_verify:
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def log_action(msg):
    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    line = f"[{ts}] {msg}"
    print(line)
    try:
        with open(PATHS.trading_log, "a", encoding="utf-8") as f:
            f.write(line + "\n")
    except Exception as exc:
        print(f"[{ts}] LOGGING_FEHLER: {exc}")


def handle_alert(alert):
    log_action(f"📣 ALERT PLAN: {alert.text}")
    result = send_telegram_alert(alert, TELEGRAM_CONFIG)
    if result.sent:
        log_action(f"📨 Telegram Alert gesendet: message_id={result.message_id}")
    elif result.reason != "disabled":
        log_action(f"⚠️ Telegram Alert nicht gesendet: {result.reason}")

try:
    # --- V61 TIGHT SURVIVAL ENGINE (Aggressives Risk-Management) ---
    MAX_TOTAL_TRADES = CONFIG.max_total_trades
    DEFAULT_LEVERAGE = CONFIG.default_leverage
    TRADE_SIZE_USD = CONFIG.trade_size_usd

    CRASH_WINDOW_MINUTES = CONFIG.crash_window_minutes
    FLASH_CRASH_TRIGGER_PCT = CONFIG.flash_crash_trigger_pct
    
    # V61 Aggressive Exit Parameter (Ungehebelt)
    ATR_SL_MULTIPLIER = CONFIG.atr_sl_multiplier
    BREAK_EVEN_ACTIVATION_PCT = CONFIG.break_even_activation_pct
    V_SHAPE_ACTIVATION_PCT = CONFIG.v_shape_activation_pct
    V_SHAPE_TRAIL_DIST_PCT = CONFIG.v_shape_trail_dist_pct
    DEAD_FISH_TIME_LIMIT_MINS = CONFIG.dead_fish_time_limit_mins
    
    MAX_HARD_STOP_PCT = CONFIG.max_hard_stop_pct

    MIN_VOLUME_24H = CONFIG.min_volume_24h
    BLACKLIST = list(CONFIG.blacklist)
    COOLDOWN_MINUTES = CONFIG.cooldown_minutes

    LIVE_PRICES = {}
    PRICE_HISTORY = {} 
    VOLUMES = {}
    SZ_DECIMALS = {}
    BASELINE_CACHE = BaselinePriceCache(ttl_seconds=CONFIG.baseline_cache_ttl_seconds)
    ERROR_LAST_LOG = {}
    NEAR_MISS_LAST_LOG = {}

    def ccxt_sym(coin): return f"{coin}/USDC:USDC"

    def create_exchange():
        if CONFIG.paper_trading:
            log_action(f"🧪 PAPER TRADING aktiv: State={PATHS.paper_state}")
            return PaperExchange(PATHS.paper_state)
        load_dotenv()
        secret_key = os.getenv("HL_API_PRIVATE_KEY")
        wallet_address = os.getenv("HL_WALLET_ADDRESS")
        if not secret_key or not wallet_address:
            raise RuntimeError("HL_API_PRIVATE_KEY oder HL_WALLET_ADDRESS fehlen in der .env")
        exchange = ccxt.hyperliquid({
            'walletAddress': wallet_address,
            'privateKey': secret_key,
            'enableRateLimit': True,
            'options': {'defaultType': 'swap'}
        })
        exchange.session.verify = CONFIG.tls_verify
        exchange.load_markets()
        return exchange

    def require_executor():
        if EXECUTOR is None:
            raise RuntimeError("TradingExecutor wurde nicht initialisiert")
        return EXECUTOR

    def load_json(file_path):
        if os.path.exists(file_path):
            try:
                with open(file_path, "r") as f: return json.load(f)
            except Exception as exc:
                log_action(f"⚠️ JSON konnte nicht geladen werden ({file_path}): {exc}")
                return {}
        return {}

    def save_json(file_path, data):
        with open(file_path, "w") as f: json.dump(data, f, indent=4)

    def on_message(ws, message):
        try:
            data = json.loads(message)
            if data.get("channel") == "allMids":
                for c, p in data["data"]["mids"].items():
                    LIVE_PRICES[c] = float(p)
        except Exception as exc:
            log_action(f"⚠️ WebSocket message ignoriert: {exc}")

    def start_ws():
        while True:
            try:
                ws = websocket.WebSocketApp(
                    "wss://api.hyperliquid.xyz/ws", 
                    on_message=on_message, 
                    on_open=lambda ws: ws.send(json.dumps({"method": "subscribe", "subscription": {"type": "allMids"}}))
                )
                ws.run_forever(sslopt={"cert_reqs": ssl.CERT_REQUIRED if CONFIG.tls_verify else ssl.CERT_NONE})
            except Exception as exc:
                log_action(f"⚠️ WebSocket reconnect nach Fehler: {exc}")
                time.sleep(2)

    def get_atr(coin, current_px):
        try:
            end_time = int(time.time() * 1000)
            start_time = end_time - (24 * 60 * 60 * 1000)
            payload = {"type": "candleSnapshot", "req": {"coin": coin, "interval": "15m", "startTime": start_time, "endTime": end_time}}
            r = requests.post("https://api.hyperliquid.xyz/info", headers={"Content-Type": "application/json"}, json=payload, verify=CONFIG.tls_verify)
            candles = r.json()
            if not candles or len(candles) < 15: return current_px * 0.02
            closes = [float(c["c"]) for c in candles]
            highs = [float(c["h"]) for c in candles]
            lows = [float(c["l"]) for c in candles]
            trueranges = []
            for i in range(1, len(closes)):
                tr = max(highs[i] - lows[i], abs(highs[i] - closes[i-1]), abs(lows[i] - closes[i-1]))
                trueranges.append(tr)
            return sum(trueranges[-14:]) / 14 if len(trueranges) >= 14 else (highs[-1]-lows[-1])
        except Exception: return current_px * 0.02

    def fetch_baseline_price(coin):
        end_time = int(time.time() * 1000)
        start_time = end_time - (75 * 60 * 1000)
        payload = {"type": "candleSnapshot", "req": {"coin": coin, "interval": "15m", "startTime": start_time, "endTime": end_time}}
        r = requests.post("https://api.hyperliquid.xyz/info", headers={"Content-Type": "application/json"}, json=payload, verify=CONFIG.tls_verify, timeout=10)
        candles = r.json()
        if candles and len(candles) > 0:
            return float(candles[0]["o"])
        return 0.0

    def get_baseline_price(coin):
        try:
            return BASELINE_CACHE.get(coin, now_ts=time.time(), fetcher=fetch_baseline_price)
        except Exception as exc:
            if should_log_error(f"baseline:{coin}", now_ts=time.time(), last_log=ERROR_LAST_LOG, interval_seconds=CONFIG.error_log_throttle_seconds):
                log_action(f"⚠️ Baseline-Preis nicht abrufbar für {coin}: {exc}")
        return 0.0

    def main():
        global EXECUTOR
        log_action(f"⚡ {STRATEGY_ID} gestartet (Dry-run={CONFIG.dry_run}, Paper={CONFIG.paper_trading}, TLS verify={CONFIG.tls_verify})...")
        exchange = create_exchange()
        EXECUTOR = TradingExecutor(exchange, dry_run=CONFIG.dry_run)
        threading.Thread(target=start_ws, daemon=True).start()
        
        last_api_update = 0; last_scan_log = 0; last_history_cleanup = 0
        open_positions = {}
        memory_state = {}
        cooldowns = load_json(str(PATHS.cooldowns))
        
        while not LIVE_PRICES: time.sleep(0.1)
        
        while True:
            try:
                current_ts = time.time()

                if current_ts - last_api_update > 10:
                    try:
                        r = requests.post("https://api.hyperliquid.xyz/info", headers={"Content-Type": "application/json"}, json={"type": "metaAndAssetCtxs"}, verify=CONFIG.tls_verify, timeout=10)
                        if r.status_code == 200:
                            data = r.json()
                            for i, coin_info in enumerate(data[0]["universe"]):
                                coin_name = coin_info["name"]
                                VOLUMES[coin_name] = float(data[1][i]["dayNtlVlm"])
                                SZ_DECIMALS[coin_name] = coin_info["szDecimals"]
                        
                        ccxt_positions = exchange.fetch_positions()
                        new_open = {}
                        for p in ccxt_positions:
                            contracts = float(p.get('contracts', 0))
                            if contracts > 0:
                                coin = p['symbol'].split('/')[0]
                                new_open[coin] = {
                                    "entry_px": float(p['entryPrice']), "leverage": float(p['leverage']),
                                    "dir": p['side'], "szi": contracts if p['side'] == 'long' else -contracts
                                }
                                if coin not in memory_state:
                                    atr = get_atr(coin, float(p['entryPrice']))
                                    recovered_state, reconstructed = recover_position_state(p, now_ts=current_ts, cfg=CONFIG, atr=atr)
                                    memory_state[coin] = recovered_state
                                    if reconstructed and should_log_error(f"restart_state:{coin}", now_ts=current_ts, last_log=ERROR_LAST_LOG, interval_seconds=CONFIG.error_log_throttle_seconds):
                                        log_action(f"⚠️ Position-State rekonstruiert für {coin}; konservative Time-Stop/ATR Defaults aktiv.")
                        open_positions = new_open
                        last_api_update = current_ts
                    except Exception as exc:
                        if should_log_error("meta_positions", now_ts=current_ts, last_log=ERROR_LAST_LOG, interval_seconds=CONFIG.error_log_throttle_seconds):
                            log_action(f"⚠️ API/Positions-Update fehlgeschlagen: {exc}")

                scan_coins = select_scan_coins(VOLUMES, CONFIG)
                scan_coin_set = set(scan_coins)
                for coin, px in LIVE_PRICES.items():
                    if coin not in scan_coin_set:
                        continue
                    if coin not in PRICE_HISTORY: PRICE_HISTORY[coin] = []
                    PRICE_HISTORY[coin].append((current_ts, px))

                if current_ts - last_history_cleanup > 10:
                    cutoff_time = current_ts - (CRASH_WINDOW_MINUTES * 60)
                    for coin in list(PRICE_HISTORY.keys()):
                        if coin not in scan_coin_set:
                            PRICE_HISTORY.pop(coin, None)
                            continue
                        if CONFIG.strategy_family in {"volatility_squeeze_breakout", "confirmed_squeeze_breakout", "hybrid_survival_squeeze"}:
                            PRICE_HISTORY[coin] = PRICE_HISTORY[coin][-max(30, CONFIG.squeeze_lookback_ticks + 1):]
                        elif CONFIG.strategy_family in {"trend_pullback_sma_vwap", "bollinger_rsi_mean_reversion", "multi_day_trend_investment", "relative_strength_rotation"}:
                            PRICE_HISTORY[coin] = PRICE_HISTORY[coin][-1200:]
                        else:
                            PRICE_HISTORY[coin] = [x for x in PRICE_HISTORY[coin] if x[0] >= cutoff_time]
                    last_history_cleanup = current_ts

                if current_ts - last_scan_log > 60:
                    metrics = calculate_daily_metrics(PATHS.trade_journal)
                    log_action(f"🕸️ NETZ AKTIV ({STRATEGY_ID}): Scanne {len(scan_coin_set)} Coins. Trades: {len(open_positions)}/{MAX_TOTAL_TRADES}. Heute: {metrics.closed_trade_count} closed / {metrics.realized_pnl_usd:+.2f} USDC.")
                    last_scan_log = current_ts

                # 3. V61 EXIT-LOGIK
                closed_coins = []
                for coin, pos_data in list(open_positions.items()):
                    curr_px = LIVE_PRICES.get(coin, 0)
                    if curr_px == 0 or coin not in memory_state: continue
                    
                    state = memory_state[coin]
                    entry_px = pos_data["entry_px"]
                    leverage = pos_data["leverage"]
                    
                    action_trigger = None; reason = ""

                    if pos_data["dir"] == "long":
                        was_be_active = state.be_active
                        was_trailing_active = state.trailing_active
                        exit_decision = exit_long_position(
                            entry_px=entry_px,
                            current_px=curr_px,
                            leverage=leverage,
                            state=state,
                            now_ts=current_ts,
                            cfg=CONFIG,
                        )
                        if state.be_active and not was_be_active:
                            log_action(f"🛡️ BREAK-EVEN SNAP für {coin}! Stop auf {entry_px} nachgezogen.")
                        if state.trailing_active and not was_trailing_active:
                            log_action(f"🔥 V-SHAPE TRAILING AKTIVIERT für {coin}!")
                        update_position_state = getattr(exchange, "update_position_state", None)
                        if callable(update_position_state):
                            update_position_state(coin, {
                                "entryTs": state.entry_ts,
                                "highPrice": state.high_px,
                                "atrSlPx": state.atr_sl_px,
                                "beActive": state.be_active,
                                "trailingActive": state.trailing_active,
                            })
                        if exit_decision.close:
                            action_trigger = "close"; reason = exit_decision.reason

                    if action_trigger == "close":
                        try:
                            symbol = ccxt_sym(coin)
                            decimals = SZ_DECIMALS.get(coin, 0)
                            size = round(abs(pos_data["szi"]), decimals) if decimals > 0 else int(abs(pos_data["szi"]))
                            require_executor().create_order(symbol, 'market', 'sell', size, curr_px, params={'reduceOnly': True})
                            entry_for_pnl = float(pos_data.get("entry_px", curr_px))
                            realized_pnl_usd = ((curr_px - entry_for_pnl) * size) if pos_data.get("dir") == "long" else ((entry_for_pnl - curr_px) * size)
                            append_journal_event(
                                PATHS.trade_journal,
                                event_type="exit",
                                coin=coin,
                                side=pos_data.get("dir", "long"),
                                price=curr_px,
                                size=size,
                                dry_run=CONFIG.dry_run,
                                reason=reason,
                                realized_pnl_usd=realized_pnl_usd,
                                extra={"strategy_id": STRATEGY_ID},
                            )
                            alert = build_trade_alert(event_type="exit", coin=coin, side=pos_data.get("dir", "long"), price=curr_px, size=size, dry_run=CONFIG.dry_run, paper_trading=CONFIG.paper_trading, reason=reason, realized_pnl_usd=realized_pnl_usd)
                            log_action(f"✅ EXIT {coin}: {reason}{' [DRY-RUN]' if CONFIG.dry_run else ''}")
                            handle_alert(alert)
                            closed_coins.append(coin)
                            cooldowns[coin] = current_ts
                        except Exception as e: log_action(f"❌ EXIT FEHLER {coin}: {e}")
                
                for c in closed_coins: 
                    open_positions.pop(c, None); memory_state.pop(c, None)
                if closed_coins: save_json(str(PATHS.cooldowns), cooldowns)

                # 4. ENTRY LOGIK
                daily_metrics = calculate_daily_metrics(PATHS.trade_journal)
                risk_decision = risk_gate(
                    daily_pnl_usd=daily_metrics.realized_pnl_usd,
                    daily_trade_count=daily_metrics.closed_trade_count,
                    kill_switch_path=PATHS.kill_switch,
                    cfg=CONFIG,
                )
                if not risk_decision.allowed:
                    if current_ts - last_scan_log > 60:
                        alert = build_risk_alert(reason=risk_decision.reason, dry_run=CONFIG.dry_run, paper_trading=CONFIG.paper_trading)
                        log_action(f"⛔ Risk Gate blockiert Entries: {risk_decision.reason}")
                        handle_alert(alert)
                elif len(open_positions) < MAX_TOTAL_TRADES:
                    for coin, hist in list(PRICE_HISTORY.items()):
                        if CONFIG.strategy_family in {"volatility_squeeze_breakout", "confirmed_squeeze_breakout"}:
                            min_history = max(2, CONFIG.squeeze_lookback_ticks)
                        elif CONFIG.strategy_family == "hybrid_survival_squeeze":
                            min_history = max(30, CONFIG.squeeze_lookback_ticks)
                        elif CONFIG.strategy_family in {"trend_pullback_sma_vwap", "bollinger_rsi_mean_reversion"}:
                            min_history = 8
                        elif CONFIG.strategy_family == "multi_day_trend_investment":
                            min_history = 10
                        elif CONFIG.strategy_family == "relative_strength_rotation":
                            min_history = max(2, CONFIG.relative_strength_lookback_ticks + 1)
                        else:
                            min_history = 30
                        if len(hist) < min_history: continue
                        curr_px = LIVE_PRICES.get(coin, 0)
                        baseline_px = get_baseline_price(coin)
                        entry_decision = should_enter_by_strategy_family(
                            coin=coin,
                            history=hist,
                            current_price=curr_px,
                            volume_24h=VOLUMES.get(coin, 0),
                            baseline_price=baseline_px,
                            open_positions=set(open_positions.keys()),
                            cooldowns=cooldowns,
                            now_ts=current_ts,
                            cfg=CONFIG,
                            market_histories=PRICE_HISTORY,
                        )
                        if not entry_decision.enter:
                            if entry_decision.reason in {"no_flash_crash", "above_baseline"} and entry_decision.drop_pct < 0:
                                near_miss = build_near_miss_event(
                                    strategy_id=STRATEGY_ID,
                                    coin=coin,
                                    history=hist,
                                    current_price=curr_px,
                                    reason=entry_decision.reason,
                                    cfg=CONFIG,
                                    now_ts=current_ts,
                                )
                                if near_miss and should_log_error(f"near_miss:{coin}", now_ts=current_ts, last_log=NEAR_MISS_LAST_LOG, interval_seconds=60):
                                    append_near_miss(PATHS.near_miss_journal, near_miss)
                            continue
                                
                        symbol = ccxt_sym(coin)
                        try:
                            coin_leverage = leverage_for_coin(CONFIG, coin)
                            require_executor().set_leverage(coin_leverage, symbol)
                            atr = get_atr(coin, curr_px)
                            atr_stop_pct = (ATR_SL_MULTIPLIER * atr / curr_px) * 100.0 if curr_px > 0 else MAX_HARD_STOP_PCT
                            stop_distance_pct = max(0.01, min(MAX_HARD_STOP_PCT, atr_stop_pct))
                            sizing = calculate_position_size(
                                coin=coin,
                                current_price=curr_px,
                                leverage=coin_leverage,
                                stop_distance_pct=stop_distance_pct,
                                cfg=CONFIG,
                            )
                            size = round(max(sizing.size, CONFIG.min_order_notional_usd / curr_px), SZ_DECIMALS.get(coin, 0))
                            require_executor().create_order(symbol, 'market', 'buy', size, curr_px, params={
                                'paper_position_state': {
                                    'entryTs': current_ts,
                                    'highPrice': curr_px,
                                    'atrSlPx': curr_px - (ATR_SL_MULTIPLIER * atr),
                                    'beActive': False,
                                    'trailingActive': False,
                                }
                            })
                            append_journal_event(
                                PATHS.trade_journal,
                                event_type="entry",
                                coin=coin,
                                side="long",
                                price=curr_px,
                                size=size,
                                dry_run=CONFIG.dry_run,
                                reason=entry_decision.reason,
                                extra={"drop_pct": entry_decision.drop_pct, "roe_drop_pct": entry_decision.roe_drop_pct, "required_drop_pct": entry_decision.required_drop_pct, "leverage": coin_leverage, "strategy_id": STRATEGY_ID, "position_sizing_mode": CONFIG.position_sizing_mode, "notional_usd": sizing.notional_usd, "margin_usd": sizing.margin_usd, "risk_usd": sizing.risk_usd, "stop_distance_pct": sizing.stop_distance_pct, "sizing_cap": sizing.capped_by},
                            )
                            alert = build_trade_alert(event_type="entry", coin=coin, side="long", price=curr_px, size=size, dry_run=CONFIG.dry_run, paper_trading=CONFIG.paper_trading, reason=entry_decision.reason)
                            log_action(f"💥 {STRATEGY_ID} ENTRY: {coin} {entry_decision.reason}! Notional: {sizing.notional_usd:.2f} USDC, Margin: {sizing.margin_usd:.2f}, Risk: {sizing.risk_usd:.2f}.{' [DRY-RUN]' if CONFIG.dry_run else ''}")
                            handle_alert(alert)
                            open_positions[coin] = {"entry_px": curr_px, "leverage": coin_leverage, "dir": "long", "szi": size}
                            memory_state[coin] = PositionState(
                                entry_ts=current_ts,
                                high_px=curr_px,
                                atr_sl_px=curr_px - (ATR_SL_MULTIPLIER * atr),
                            )
                            PRICE_HISTORY[coin] = []; break
                        except Exception as e: log_action(f"❌ ENTRY FEHLER {coin}: {e}")

                time.sleep(0.1) 
            except Exception: time.sleep(1)

    if __name__ == "__main__":
        require_legacy_autotrader_enabled()
        main()
except Exception as e:
    log_action(f"🔥 FATALER FEHLER:\n{traceback.format_exc()}")
