"""
Signal Logger - Phase 2
Logging signals in conversational format for LLM fine-tuning
"""

import json
import logging
from dataclasses import dataclass, asdict
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Any, Union
import pandas as pd


logger = logging.getLogger(__name__)


@dataclass
class ConversationTurn:
    """Single turn in a conversation"""
    role: str  # "system", "user", "assistant"
    content: str


@dataclass
class ConversationLog:
    """Complete conversation log for fine-tuning"""
    id: str
    timestamp: str
    symbol: str
    horizon: str
    turns: List[Dict[str, str]]
    metadata: Dict[str, Any]

    def to_dict(self) -> Dict:
        return asdict(self)

    def to_jsonl_line(self) -> str:
        """Format for JSONL fine-tuning"""
        return json.dumps(self.to_dict(), ensure_ascii=False, default=str)


class SignalLogger:
    """
    Logger for trading signals in conversational format for LLM fine-tuning.

    Generates JSONL files with conversations that can be used to fine-tune
    LLMs on trading signal interpretation and decision making.
    """

    def __init__(
        self,
        output_dir: str = "logs/signals",
        system_prompt: Optional[str] = None
    ):
        """
        Initialize SignalLogger.

        Args:
            output_dir: Directory to save log files
            system_prompt: System prompt for conversations
        """
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)

        self.system_prompt = system_prompt or self._default_system_prompt()
        self.conversations: List[ConversationLog] = []

    def _default_system_prompt(self) -> str:
        """Default system prompt for trading conversations"""
        return """You are a professional trading analyst specializing in XAUUSD (Gold).
Your role is to analyze trading signals and provide clear, actionable recommendations.

You receive signals with the following information:
- Direction (long/short)
- Entry price, stop loss, and take profit levels
- Probability of hitting TP before SL
- Market phase (accumulation, manipulation, distribution)
- Volatility regime (low, medium, high)
- Range predictions for price movement

Based on this information, you should:
1. Evaluate the signal quality
2. Assess risk/reward
3. Consider market context
4. Provide a clear recommendation with reasoning"""

    def _format_signal_as_user_message(self, signal: Dict) -> str:
        """Format a trading signal as a user query"""
        msg = f"""New trading signal received for {signal.get('symbol', 'XAUUSD')}:

**Signal Details:**
- Direction: {signal.get('direction', 'N/A').upper()}
- Entry Price: ${signal.get('entry_price', 0):.2f}
- Stop Loss: ${signal.get('stop_loss', 0):.2f}
- Take Profit: ${signal.get('take_profit', 0):.2f}
- Expected R:R: {signal.get('expected_rr', 0):.1f}:1

**Probabilities:**
- P(TP First): {signal.get('prob_tp_first', 0)*100:.1f}%
- Confidence: {signal.get('confidence_score', 0)*100:.1f}%

**Market Context:**
- AMD Phase: {signal.get('phase_amd', 'N/A')}
- Volatility: {signal.get('volatility_regime', 'N/A')}
- Horizon: {signal.get('horizon_minutes', 0)} minutes

**Range Prediction:**
- Expected High Move: +${signal.get('range_prediction', {}).get('delta_high', 0):.2f}
- Expected Low Move: -${signal.get('range_prediction', {}).get('delta_low', 0):.2f}

Should I take this trade? Please analyze the signal and provide your recommendation."""

        return msg

    def _format_assistant_response(
        self,
        signal: Dict,
        recommendation: str,
        reasoning: List[str],
        risk_assessment: str,
        additional_notes: Optional[str] = None
    ) -> str:
        """Format the assistant's analysis response"""
        direction = signal.get('direction', 'long')
        prob_tp = signal.get('prob_tp_first', 0)
        confidence = signal.get('confidence_score', 0)
        phase = signal.get('phase_amd', 'neutral')
        volatility = signal.get('volatility_regime', 'medium')
        rr = signal.get('expected_rr', 2.0)

        # Build reasoning section
        reasoning_text = "\n".join([f"- {r}" for r in reasoning])

        response = f"""## Signal Analysis

### Recommendation: **{recommendation.upper()}**

### Key Factors:
{reasoning_text}

### Risk Assessment:
{risk_assessment}

### Technical Summary:
- The signal suggests a **{direction}** position with a {rr:.1f}:1 reward-to-risk ratio.
- Probability of success (TP first): {prob_tp*100:.1f}%
- Signal confidence: {confidence*100:.1f}%
- Current market phase: {phase} with {volatility} volatility."""

        if additional_notes:
            response += f"\n\n### Additional Notes:\n{additional_notes}"

        return response

    def log_signal(
        self,
        signal: Dict,
        outcome: Optional[Dict] = None,
        custom_analysis: Optional[Dict] = None
    ) -> ConversationLog:
        """
        Log a trading signal as a conversation.

        Args:
            signal: Trading signal dictionary
            outcome: Optional actual trade outcome
            custom_analysis: Optional custom analysis override

        Returns:
            ConversationLog object
        """
        # Generate conversation ID
        timestamp = datetime.utcnow()
        conv_id = f"signal_{signal.get('symbol', 'XAUUSD')}_{timestamp.strftime('%Y%m%d_%H%M%S')}"

        # Build conversation turns
        turns = []

        # System turn
        turns.append({
            "role": "system",
            "content": self.system_prompt
        })

        # User turn (signal query)
        turns.append({
            "role": "user",
            "content": self._format_signal_as_user_message(signal)
        })

        # Generate or use custom analysis
        if custom_analysis:
            recommendation = custom_analysis.get('recommendation', 'HOLD')
            reasoning = custom_analysis.get('reasoning', [])
            risk_assessment = custom_analysis.get('risk_assessment', '')
            additional_notes = custom_analysis.get('additional_notes')
        else:
            # Auto-generate analysis based on signal
            recommendation, reasoning, risk_assessment = self._auto_analyze(signal)
            additional_notes = None

        # Assistant turn (analysis)
        turns.append({
            "role": "assistant",
            "content": self._format_assistant_response(
                signal, recommendation, reasoning, risk_assessment, additional_notes
            )
        })

        # If we have outcome, add follow-up
        if outcome:
            turns.append({
                "role": "user",
                "content": f"Update: The trade has closed. Result: {outcome.get('result', 'N/A')}"
            })

            outcome_analysis = self._format_outcome_response(signal, outcome)
            turns.append({
                "role": "assistant",
                "content": outcome_analysis
            })

        # Build metadata
        metadata = {
            "signal_timestamp": signal.get('timestamp', timestamp.isoformat()),
            "direction": signal.get('direction'),
            "entry_price": signal.get('entry_price'),
            "prob_tp_first": signal.get('prob_tp_first'),
            "confidence_score": signal.get('confidence_score'),
            "phase_amd": signal.get('phase_amd'),
            "volatility_regime": signal.get('volatility_regime'),
            "recommendation": recommendation,
            "outcome": outcome
        }

        # Create conversation log
        conv_log = ConversationLog(
            id=conv_id,
            timestamp=timestamp.isoformat(),
            symbol=signal.get('symbol', 'XAUUSD'),
            horizon=f"{signal.get('horizon_minutes', 60)}m",
            turns=turns,
            metadata=metadata
        )

        self.conversations.append(conv_log)
        return conv_log

    def _auto_analyze(self, signal: Dict) -> tuple:
        """Auto-generate analysis based on signal parameters"""
        prob_tp = signal.get('prob_tp_first', 0.5)
        confidence = signal.get('confidence_score', 0.5)
        phase = signal.get('phase_amd', 'neutral')
        volatility = signal.get('volatility_regime', 'medium')
        rr = signal.get('expected_rr', 2.0)
        direction = signal.get('direction', 'none')

        reasoning = []

        # Probability assessment
        if prob_tp >= 0.6:
            reasoning.append(f"High probability of success ({prob_tp*100:.0f}%) suggests favorable odds")
        elif prob_tp >= 0.5:
            reasoning.append(f"Moderate probability ({prob_tp*100:.0f}%) indicates balanced risk")
        else:
            reasoning.append(f"Lower probability ({prob_tp*100:.0f}%) warrants caution")

        # Confidence assessment
        if confidence >= 0.7:
            reasoning.append(f"High model confidence ({confidence*100:.0f}%) supports the signal")
        elif confidence >= 0.55:
            reasoning.append(f"Moderate confidence ({confidence*100:.0f}%) is acceptable")
        else:
            reasoning.append(f"Low confidence ({confidence*100:.0f}%) suggests waiting for better setup")

        # Phase assessment
        phase_analysis = {
            'accumulation': f"Accumulation phase favors {'long' if direction == 'long' else 'contrarian'} positions",
            'distribution': f"Distribution phase favors {'short' if direction == 'short' else 'contrarian'} positions",
            'manipulation': "Manipulation phase suggests increased volatility and false moves",
            'neutral': "Neutral phase provides no directional bias"
        }
        reasoning.append(phase_analysis.get(phase, "Phase analysis unavailable"))

        # R:R assessment
        if rr >= 2.5:
            reasoning.append(f"Excellent risk/reward ratio of {rr:.1f}:1")
        elif rr >= 2.0:
            reasoning.append(f"Good risk/reward ratio of {rr:.1f}:1")
        else:
            reasoning.append(f"Acceptable risk/reward ratio of {rr:.1f}:1")

        # Generate recommendation
        score = (prob_tp * 0.4) + (confidence * 0.3) + (min(rr, 3) / 3 * 0.3)

        if direction == 'none':
            recommendation = "NO TRADE"
            risk_assessment = "No clear directional signal. Recommend staying flat."
        elif score >= 0.65 and prob_tp >= 0.55:
            recommendation = "TAKE TRADE"
            risk_assessment = f"Favorable setup with acceptable risk. Use standard position sizing."
        elif score >= 0.5:
            recommendation = "CONSIDER"
            risk_assessment = "Marginal setup. Consider reduced position size or additional confirmation."
        else:
            recommendation = "PASS"
            risk_assessment = "Unfavorable risk/reward profile. Wait for better opportunity."

        # Adjust for volatility
        if volatility == 'high':
            risk_assessment += " Note: High volatility environment - consider wider stops or smaller size."

        return recommendation, reasoning, risk_assessment

    def _format_outcome_response(self, signal: Dict, outcome: Dict) -> str:
        """Format response after trade outcome"""
        result = outcome.get('result', 'unknown')
        pnl = outcome.get('pnl', 0)
        duration = outcome.get('duration_minutes', 0)

        if result == 'tp_hit':
            response = f"""## Trade Result: **WIN** ✓

The trade reached the take profit target.
- P&L: +${pnl:.2f}
- Duration: {duration} minutes

### Post-Trade Analysis:
The signal correctly identified the market direction. The probability estimate of {signal.get('prob_tp_first', 0)*100:.0f}% aligned with the outcome."""

        elif result == 'sl_hit':
            response = f"""## Trade Result: **LOSS** ✗

The trade was stopped out.
- P&L: -${abs(pnl):.2f}
- Duration: {duration} minutes

### Post-Trade Analysis:
Despite the setup, market moved against the position. This is within expected outcomes given the {signal.get('prob_tp_first', 0)*100:.0f}% probability estimate."""

        else:
            response = f"""## Trade Result: **{result.upper()}**

- P&L: ${pnl:.2f}
- Duration: {duration} minutes

Trade closed without hitting either target."""

        return response

    def log_batch(
        self,
        signals: List[Dict],
        outcomes: Optional[List[Dict]] = None
    ) -> List[ConversationLog]:
        """Log multiple signals"""
        outcomes = outcomes or [None] * len(signals)
        logs = []

        for signal, outcome in zip(signals, outcomes):
            log = self.log_signal(signal, outcome)
            logs.append(log)

        return logs

    def save_jsonl(
        self,
        filename: Optional[str] = None,
        append: bool = False
    ) -> Path:
        """
        Save conversations to JSONL file.

        Args:
            filename: Output filename (auto-generated if None)
            append: Append to existing file

        Returns:
            Path to saved file
        """
        if filename is None:
            filename = f"signals_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.jsonl"

        filepath = self.output_dir / filename
        mode = 'a' if append else 'w'

        with open(filepath, mode, encoding='utf-8') as f:
            for conv in self.conversations:
                f.write(conv.to_jsonl_line() + '\n')

        logger.info(f"Saved {len(self.conversations)} conversations to {filepath}")
        return filepath

    def save_openai_format(
        self,
        filename: Optional[str] = None
    ) -> Path:
        """
        Save in OpenAI fine-tuning format (messages array only).

        Args:
            filename: Output filename

        Returns:
            Path to saved file
        """
        if filename is None:
            filename = f"signals_openai_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.jsonl"

        filepath = self.output_dir / filename

        with open(filepath, 'w', encoding='utf-8') as f:
            for conv in self.conversations:
                # OpenAI format: {"messages": [...]}
                openai_format = {"messages": conv.turns}
                f.write(json.dumps(openai_format, ensure_ascii=False) + '\n')

        logger.info(f"Saved {len(self.conversations)} conversations in OpenAI format to {filepath}")
        return filepath

    def save_anthropic_format(
        self,
        filename: Optional[str] = None
    ) -> Path:
        """
        Save in Anthropic fine-tuning format.

        Args:
            filename: Output filename

        Returns:
            Path to saved file
        """
        if filename is None:
            filename = f"signals_anthropic_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.jsonl"

        filepath = self.output_dir / filename

        with open(filepath, 'w', encoding='utf-8') as f:
            for conv in self.conversations:
                # Anthropic format separates system prompt
                system = None
                messages = []

                for turn in conv.turns:
                    if turn['role'] == 'system':
                        system = turn['content']
                    else:
                        messages.append({
                            "role": turn['role'],
                            "content": turn['content']
                        })

                anthropic_format = {
                    "system": system,
                    "messages": messages
                }
                f.write(json.dumps(anthropic_format, ensure_ascii=False) + '\n')

        logger.info(f"Saved {len(self.conversations)} conversations in Anthropic format to {filepath}")
        return filepath

    def clear(self):
        """Clear stored conversations"""
        self.conversations = []

    def get_statistics(self) -> Dict:
        """Get logging statistics"""
        if not self.conversations:
            return {"total": 0}

        recommendations = {}
        symbols = {}
        horizons = {}

        for conv in self.conversations:
            rec = conv.metadata.get('recommendation', 'UNKNOWN')
            recommendations[rec] = recommendations.get(rec, 0) + 1

            sym = conv.symbol
            symbols[sym] = symbols.get(sym, 0) + 1

            hor = conv.horizon
            horizons[hor] = horizons.get(hor, 0) + 1

        return {
            "total": len(self.conversations),
            "by_recommendation": recommendations,
            "by_symbol": symbols,
            "by_horizon": horizons
        }


def create_training_dataset(
    signals_df: pd.DataFrame,
    outcomes_df: Optional[pd.DataFrame] = None,
    output_dir: str = "logs/training",
    formats: List[str] = ["jsonl", "openai", "anthropic"]
) -> Dict[str, Path]:
    """
    Create training dataset from signals DataFrame.

    Args:
        signals_df: DataFrame with trading signals
        outcomes_df: Optional DataFrame with trade outcomes
        output_dir: Output directory
        formats: Output formats to generate

    Returns:
        Dictionary mapping format names to file paths
    """
    logger_instance = SignalLogger(output_dir=output_dir)

    # Convert DataFrame rows to signal dictionaries
    signals = signals_df.to_dict(orient='records')

    outcomes = None
    if outcomes_df is not None:
        outcomes = outcomes_df.to_dict(orient='records')

    # Log all signals
    logger_instance.log_batch(signals, outcomes)

    # Save in requested formats
    output_files = {}

    if "jsonl" in formats:
        output_files["jsonl"] = logger_instance.save_jsonl()

    if "openai" in formats:
        output_files["openai"] = logger_instance.save_openai_format()

    if "anthropic" in formats:
        output_files["anthropic"] = logger_instance.save_anthropic_format()

    return output_files


# Export for easy import
__all__ = [
    'SignalLogger',
    'ConversationLog',
    'ConversationTurn',
    'create_training_dataset'
]
