#!/usr/bin/env python3
"""
Crypto Signal Backtester
Simulates each signal against historical price data to determine outcomes.
"""

import json
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
from price_fetcher import get_all_klines, get_current_price, normalize_symbol, datetime_to_ms


def simulate_signal(signal, klines):
    """
    Simulate a signal against historical candle data.
    Returns outcome dict with result, P&L, time to resolution, etc.
    """
    direction = signal['direction']
    entry = signal.get('entry')
    stop_loss = signal.get('stop_loss')
    targets = signal.get('targets', [])
    leverage = signal.get('leverage', 1)
    
    if not targets or not stop_loss:
        return {'result': 'incomplete', 'reason': 'missing SL or targets'}
    
    target = targets[0]  # Primary target
    
    # If entry is 'market', use first candle's open
    if entry == 'market' or entry is None:
        if not klines:
            return {'result': 'no_data'}
        entry = klines[0]['open']
    
    signal['entry_resolved'] = entry
    
    # Calculate risk/reward
    if direction == 'short':
        risk = abs(stop_loss - entry)
        reward = abs(entry - target)
        risk_pct = risk / entry * 100
        reward_pct = reward / entry * 100
    else:  # long
        risk = abs(entry - stop_loss)
        reward = abs(target - entry)
        risk_pct = risk / entry * 100
        reward_pct = reward / entry * 100
    
    rr_ratio = reward / risk if risk > 0 else 0
    
    result = {
        'entry_price': entry,
        'stop_loss': stop_loss,
        'target': target,
        'direction': direction,
        'leverage': leverage,
        'risk_pct': round(risk_pct, 2),
        'reward_pct': round(reward_pct, 2),
        'rr_ratio': round(rr_ratio, 2),
    }
    
    # Walk through candles
    for i, candle in enumerate(klines):
        high = candle['high']
        low = candle['low']
        
        if direction == 'short':
            # Check SL hit (price went above SL)
            sl_hit = high >= stop_loss
            # Check TP hit (price went below target)
            tp_hit = low <= target
        else:  # long
            # Check SL hit (price went below SL)
            sl_hit = low <= stop_loss
            # Check TP hit (price went above target)
            tp_hit = high >= target
        
        if sl_hit and tp_hit:
            # Both hit in same candle — assume SL hit first (conservative)
            result['result'] = 'stop_loss'
            result['exit_price'] = stop_loss
            result['candles_to_exit'] = i + 1
            result['exit_time'] = candle['open_time']
            break
        elif tp_hit:
            result['result'] = 'target_hit'
            result['exit_price'] = target
            result['candles_to_exit'] = i + 1
            result['exit_time'] = candle['open_time']
            break
        elif sl_hit:
            result['result'] = 'stop_loss'
            result['exit_price'] = stop_loss
            result['candles_to_exit'] = i + 1
            result['exit_time'] = candle['open_time']
            break
    else:
        # Never resolved — check current unrealized P&L
        if klines:
            last_price = klines[-1]['close']
            if direction == 'short':
                unrealized_pct = (entry - last_price) / entry * 100
            else:
                unrealized_pct = (last_price - entry) / entry * 100
            result['result'] = 'open'
            result['last_price'] = last_price
            result['unrealized_pct'] = round(unrealized_pct, 2)
            result['unrealized_pct_leveraged'] = round(unrealized_pct * leverage, 2)
        else:
            result['result'] = 'no_data'
    
    # Calculate P&L
    if result['result'] in ('target_hit', 'stop_loss'):
        exit_price = result['exit_price']
        if direction == 'short':
            pnl_pct = (entry - exit_price) / entry * 100
        else:
            pnl_pct = (exit_price - entry) / entry * 100
        
        result['pnl_pct'] = round(pnl_pct, 2)
        result['pnl_pct_leveraged'] = round(pnl_pct * leverage, 2)
    
    return result


def backtest_signals(signals, interval='5m', lookforward_hours=72):
    """Backtest a list of parsed signals."""
    results = []
    
    for i, signal in enumerate(signals):
        ticker = signal['ticker']
        symbol = normalize_symbol(ticker)
        timestamp = signal.get('timestamp', '')
        
        print(f"[{i+1}/{len(signals)}] {ticker} {signal['direction']} ...", end=' ', flush=True)
        
        # Get start time
        start_ms = datetime_to_ms(timestamp) if timestamp else int(time.time() * 1000)
        end_ms = start_ms + (lookforward_hours * 60 * 60 * 1000)
        
        # Cap at current time
        now_ms = int(time.time() * 1000)
        if end_ms > now_ms:
            end_ms = now_ms
        
        # Fetch candles
        klines = get_all_klines(symbol, interval, start_ms, end_ms)
        
        if not klines:
            print(f"NO DATA")
            results.append({**signal, 'backtest': {'result': 'no_data', 'reason': f'no klines for {symbol}'}})
            continue
        
        # Simulate
        outcome = simulate_signal(signal, klines)
        print(f"{outcome['result']} | PnL: {outcome.get('pnl_pct_leveraged', outcome.get('unrealized_pct_leveraged', '?'))}%")
        
        results.append({**signal, 'backtest': outcome})
        time.sleep(0.2)  # Rate limit
    
    return results


def generate_report(results):
    """Generate a summary report from backtest results."""
    total = len(results)
    wins = [r for r in results if r['backtest'].get('result') == 'target_hit']
    losses = [r for r in results if r['backtest'].get('result') == 'stop_loss']
    open_trades = [r for r in results if r['backtest'].get('result') == 'open']
    no_data = [r for r in results if r['backtest'].get('result') in ('no_data', 'incomplete')]
    
    resolved = wins + losses
    win_rate = len(wins) / len(resolved) * 100 if resolved else 0
    
    avg_win = sum(r['backtest']['pnl_pct_leveraged'] for r in wins) / len(wins) if wins else 0
    avg_loss = sum(r['backtest']['pnl_pct_leveraged'] for r in losses) / len(losses) if losses else 0
    
    total_pnl = sum(r['backtest'].get('pnl_pct_leveraged', 0) for r in resolved)
    
    # Profit factor
    gross_profit = sum(r['backtest']['pnl_pct_leveraged'] for r in wins)
    gross_loss = abs(sum(r['backtest']['pnl_pct_leveraged'] for r in losses))
    profit_factor = gross_profit / gross_loss if gross_loss > 0 else float('inf')
    
    # Risk/reward stats
    avg_rr = sum(r['backtest'].get('rr_ratio', 0) for r in resolved) / len(resolved) if resolved else 0
    
    report = {
        'summary': {
            'total_signals': total,
            'wins': len(wins),
            'losses': len(losses),
            'open': len(open_trades),
            'no_data': len(no_data),
            'win_rate': round(win_rate, 1),
            'avg_win_pct': round(avg_win, 2),
            'avg_loss_pct': round(avg_loss, 2),
            'total_pnl_pct': round(total_pnl, 2),
            'profit_factor': round(profit_factor, 2),
            'avg_risk_reward': round(avg_rr, 2),
        },
        'trades': results,
    }
    
    return report


def print_report(report):
    """Pretty print the report."""
    s = report['summary']
    print("\n" + "=" * 60)
    print("CRYPTO SIGNAL BACKTEST REPORT")
    print("=" * 60)
    print(f"Total Signals:    {s['total_signals']}")
    print(f"Wins:             {s['wins']}")
    print(f"Losses:           {s['losses']}")
    print(f"Open:             {s['open']}")
    print(f"No Data:          {s['no_data']}")
    print(f"Win Rate:         {s['win_rate']}%")
    print(f"Avg Win:          +{s['avg_win_pct']}% (leveraged)")
    print(f"Avg Loss:         {s['avg_loss_pct']}% (leveraged)")
    print(f"Total P&L:        {s['total_pnl_pct']}% (sum of resolved)")
    print(f"Profit Factor:    {s['profit_factor']}")
    print(f"Avg R:R:          {s['avg_risk_reward']}")
    print("=" * 60)


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python3 backtester.py <signals.json> [--interval 5m] [--hours 72]")
        print("\nRun signal_parser.py first to generate signals.json")
        sys.exit(1)
    
    signals_path = sys.argv[1]
    
    interval = '5m'
    hours = 72
    for i, arg in enumerate(sys.argv):
        if arg == '--interval' and i + 1 < len(sys.argv):
            interval = sys.argv[i + 1]
        if arg == '--hours' and i + 1 < len(sys.argv):
            hours = int(sys.argv[i + 1])
    
    with open(signals_path) as f:
        signals = json.load(f)
    
    print(f"Backtesting {len(signals)} signals (interval={interval}, lookforward={hours}h)\n")
    
    results = backtest_signals(signals, interval=interval, lookforward_hours=hours)
    report = generate_report(results)
    print_report(report)
    
    # Save full report
    out_path = signals_path.replace('.json', '_backtest.json')
    with open(out_path, 'w') as f:
        json.dump(report, f, indent=2)
    print(f"\nFull report saved to {out_path}")
