"""Independent Risk Engine & Execution Gatekeeper."""
from __future__ import annotations

import uuid
from datetime import datetime, timezone
from decimal import Decimal
from typing import Optional

from backend.app.audit.logger import audit_logger
from backend.app.config.settings import settings
from backend.app.database.session import db_manager
from backend.app.risk.circuit_breakers import circuit_breakers
from backend.app.risk.kill_switch import kill_switch
from backend.app.risk.sizer import position_sizer
from backend.app.schemas.risk import AccountState, InstrumentFilters, RiskDecision
from backend.app.schemas.signal import SignalAction, SignalCandidate


class RiskEngine:
    """Independent Risk Gatekeeper with authoritative veto over all proposed trades."""

    def __init__(self) -> None:
        self.sizer = position_sizer
        self.circuit_breakers = circuit_breakers
        self.kill_switch = kill_switch

    def evaluate_signal(
        self,
        signal: SignalCandidate,
        account: AccountState,
        filters: InstrumentFilters,
    ) -> RiskDecision:
        decision_id = str(uuid.uuid4())
        now = datetime.now(timezone.utc).isoformat()

        # Rule 1: NO-TRADE signals require no execution
        if signal.action == SignalAction.NO_TRADE:
            decision = RiskDecision(
                approved=False,
                rejection_reason="Signal action is NO_TRADE.",
                signal=signal,
                equity_at_decision=account.equity,
                risk_budget=Decimal("0"),
                approved_quantity=Decimal("0"),
                entry_price=signal.entry_price,
                stop_loss_price=signal.stop_loss_price,
                take_profit_price=signal.take_profit_price,
                max_loss_estimate=Decimal("0"),
            )
            self._persist_decision(decision_id, signal.timestamp, decision, now)
            return decision

        # Rule 2: Emergency Kill Switch check
        if self.kill_switch.is_active():
            audit_logger.warning("Trade rejected: Emergency Kill Switch is engaged.")
            decision = RiskDecision(
                approved=False,
                rejection_reason="Emergency Kill Switch is currently ACTIVE.",
                signal=signal,
                equity_at_decision=account.equity,
                risk_budget=Decimal("0"),
                approved_quantity=Decimal("0"),
                entry_price=signal.entry_price,
                stop_loss_price=signal.stop_loss_price,
                take_profit_price=signal.take_profit_price,
                max_loss_estimate=Decimal("0"),
            )
            self._persist_decision(decision_id, signal.timestamp, decision, now)
            return decision

        # Rule 3: Portfolio Circuit Breakers (Daily loss, Drawdown, Max positions)
        cb_status = self.circuit_breakers.check_portfolio_limits(account)
        if cb_status.tripped:
            audit_logger.warning("Trade rejected by circuit breaker: %s", cb_status.reason)
            decision = RiskDecision(
                approved=False,
                rejection_reason=cb_status.reason,
                signal=signal,
                equity_at_decision=account.equity,
                risk_budget=Decimal("0"),
                approved_quantity=Decimal("0"),
                entry_price=signal.entry_price,
                stop_loss_price=signal.stop_loss_price,
                take_profit_price=signal.take_profit_price,
                max_loss_estimate=Decimal("0"),
            )
            self._persist_decision(decision_id, signal.timestamp, decision, now)
            return decision

        # Rule 4: Mandatory Reward-to-Risk check
        stop_dist = abs(signal.entry_price - signal.stop_loss_price)
        tp_dist = abs(signal.take_profit_price - signal.entry_price)
        if stop_dist > Decimal("0"):
            rr_ratio = float(tp_dist / stop_dist)
            if rr_ratio < settings.MIN_REWARD_TO_RISK:
                reason = f"Reward-to-risk ratio {rr_ratio:.2f} is below minimum requirement of {settings.MIN_REWARD_TO_RISK:.2f}."
                audit_logger.warning("Trade rejected: %s", reason)
                decision = RiskDecision(
                    approved=False,
                    rejection_reason=reason,
                    signal=signal,
                    equity_at_decision=account.equity,
                    risk_budget=Decimal("0"),
                    approved_quantity=Decimal("0"),
                    entry_price=signal.entry_price,
                    stop_loss_price=signal.stop_loss_price,
                    take_profit_price=signal.take_profit_price,
                    max_loss_estimate=Decimal("0"),
                )
                self._persist_decision(decision_id, signal.timestamp, decision, now)
                return decision

        # Rule 5: Position Sizing & Small Account Constraints
        sizing = self.sizer.calculate_size(
            equity=account.equity,
            entry_price=signal.entry_price,
            stop_loss_price=signal.stop_loss_price,
            filters=filters,
        )

        if not sizing.approved:
            audit_logger.info("Trade rejected by position sizer: %s", sizing.rejection_reason)
            decision = RiskDecision(
                approved=False,
                rejection_reason=sizing.rejection_reason,
                signal=signal,
                equity_at_decision=account.equity,
                risk_budget=sizing.risk_budget,
                approved_quantity=Decimal("0"),
                entry_price=signal.entry_price,
                stop_loss_price=signal.stop_loss_price,
                take_profit_price=signal.take_profit_price,
                max_loss_estimate=Decimal("0"),
            )
            self._persist_decision(decision_id, signal.timestamp, decision, now)
            return decision

        # All checks passed: APPROVE
        max_loss = sizing.quantity * sizing.effective_loss_per_unit
        decision = RiskDecision(
            approved=True,
            rejection_reason=None,
            signal=signal,
            equity_at_decision=account.equity,
            risk_budget=sizing.risk_budget,
            approved_quantity=sizing.quantity,
            entry_price=signal.entry_price,
            stop_loss_price=signal.stop_loss_price,
            take_profit_price=signal.take_profit_price,
            max_loss_estimate=max_loss,
        )
        self._persist_decision(decision_id, signal.timestamp, decision, now)
        audit_logger.info(
            "RISK APPROVED: %s %s Qty=%s RiskBudget=$%s MaxLoss=$%s",
            signal.action.value,
            signal.symbol,
            sizing.quantity,
            sizing.risk_budget,
            max_loss,
        )
        return decision

    def _persist_decision(self, decision_id: str, signal_id: str, decision: RiskDecision, timestamp: str) -> None:
        try:
            sql = """
            INSERT INTO risk_decisions (
                id, signal_id, approved, rejection_reason, equity_at_decision,
                risk_budget, approved_quantity, entry_price, stop_loss_price,
                take_profit_price, max_loss_estimate, timestamp
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """
            db_manager.execute_write(
                sql,
                (
                    decision_id,
                    signal_id,
                    1 if decision.approved else 0,
                    decision.rejection_reason,
                    str(decision.equity_at_decision),
                    str(decision.risk_budget),
                    str(decision.approved_quantity),
                    str(decision.entry_price),
                    str(decision.stop_loss_price),
                    str(decision.take_profit_price),
                    str(decision.max_loss_estimate),
                    timestamp,
                ),
            )
        except Exception as e:
            audit_logger.error("Failed to persist risk decision: %s", str(e))


risk_engine = RiskEngine()
