"""Multi-outcome event arbitrage and logical arb detection.

When N outcomes in an event have YES prices that sum to != 1.0,
there's an arbitrage opportunity. If sum < 1 - fees: buy all.
If sum > 1 + fees: sell all.

Also detects logical implication and contradiction arbs between
related markets.
"""

from __future__ import annotations

import logging
import time
from typing import Callable

from horizon.context import Context

from ._types import EventArbResult

logger = logging.getLogger(__name__)


def event_arb_scanner(
    event_id: str,
    min_edge: float = 0.01,
    max_size: float = 50.0,
    fee_rate: float = 0.002,
    auto_execute: bool = False,
    cooldown: float = 10.0,
    proportional: bool = True,
) -> Callable[[Context], None]:
    """Create a pipeline function that scans for multi-outcome event arbitrage.

    Args:
        event_id: Registered event ID.
        min_edge: Minimum net edge to consider actionable.
        max_size: Maximum total trade size.
        fee_rate: Fee rate per outcome trade.
        auto_execute: If True, execute event arb automatically.
        cooldown: Seconds between executions.
        proportional: If True, size inversely proportional to price.

    Returns:
        Pipeline function: (Context) -> None
    """
    from horizon._horizon import auth_require_pro
    auth_require_pro()

    last_exec_time = 0.0

    def _scanner(ctx: Context) -> None:
        nonlocal last_exec_time

        engine = ctx.params.get("engine")
        if engine is None:
            return None

        opp = engine.scan_event_arb(event_id, fee_rate)
        if opp is None or opp.net_edge < min_edge:
            return None

        ctx.params["last_event_arb"] = opp

        if auto_execute:
            now = time.monotonic()
            if now - last_exec_time >= cooldown:
                try:
                    order_ids = engine.execute_event_arb(
                        event_id,
                        opp.direction,
                        max_size,
                        proportional=proportional,
                    )
                    last_exec_time = now
                    ctx.params["last_event_arb_result"] = EventArbResult(
                        event_id=event_id,
                        direction=opp.direction,
                        price_sum=opp.price_sum,
                        net_edge=opp.net_edge,
                        size=max_size,
                        order_ids=order_ids,
                    )
                    logger.info(
                        "Event arb executed: %s dir=%s edge=%.4f legs=%d",
                        event_id, opp.direction, opp.net_edge, len(order_ids),
                    )
                except Exception as e:
                    logger.warning("Event arb execution failed for %s: %s", event_id, e)

        return None

    _scanner.__name__ = "event_arb_scanner"
    return _scanner


def event_arb_sweep(
    engine,
    event_id: str,
    min_edge: float = 0.01,
    max_size: float = 50.0,
    fee_rate: float = 0.002,
    proportional: bool = True,
) -> EventArbResult | None:
    """One-shot event arbitrage sweep.

    Args:
        engine: Horizon Engine instance.
        event_id: Registered event ID.
        min_edge: Minimum net edge.
        max_size: Maximum total trade size.
        fee_rate: Fee rate per outcome trade.
        proportional: If True, size inversely proportional to price.

    Returns:
        EventArbResult if executed, None if no opportunity found.
    """
    from horizon._horizon import auth_require_pro
    auth_require_pro()

    opp = engine.scan_event_arb(event_id, fee_rate)
    if opp is None or opp.net_edge < min_edge:
        return None

    try:
        order_ids = engine.execute_event_arb(
            event_id, opp.direction, max_size, proportional=proportional,
        )
    except Exception as e:
        logger.warning("Event arb sweep failed for %s: %s", event_id, e)
        return None

    return EventArbResult(
        event_id=event_id,
        direction=opp.direction,
        price_sum=opp.price_sum,
        net_edge=opp.net_edge,
        size=max_size,
        order_ids=order_ids,
    )


def implication_arb_scanner(
    markets: list[str],
    implications: list[tuple[str, str]],
    min_edge: float = 0.01,
) -> Callable[[Context], list[dict] | None]:
    """Create a pipeline function that scans for implication arbs.

    For each (A, B) where A implies B: price_A must be <= price_B.
    If price_A > price_B + min_edge, there's an arb.

    Args:
        markets: List of market IDs to monitor.
        implications: List of (A, B) tuples where A => B.
        min_edge: Minimum violation to flag.

    Returns:
        Pipeline function: (Context) -> list[dict] | None
    """

    def _scanner(ctx: Context) -> list[dict] | None:
        prices = ctx.params.get("market_prices", {})
        if not prices:
            # Try to read from feeds
            for mid in markets:
                if mid in ctx.feeds:
                    prices[mid] = ctx.feeds[mid].price

        arbs: list[dict] = []
        for a, b in implications:
            pa = prices.get(a)
            pb = prices.get(b)
            if pa is None or pb is None:
                continue
            # A => B means P(A) <= P(B)
            violation = pa - pb
            if violation > min_edge:
                arbs.append({
                    "type": "implication",
                    "market_a": a,
                    "market_b": b,
                    "price_a": pa,
                    "price_b": pb,
                    "edge": violation,
                    "action": f"sell {a} / buy {b}",
                })

        if arbs:
            ctx.params["implication_arbs"] = arbs
            return arbs
        return None

    _scanner.__name__ = "implication_arb_scanner"
    return _scanner


def contradiction_arb_scanner(
    markets: list[str],
    contradictions: list[tuple[str, str]],
    min_edge: float = 0.01,
) -> Callable[[Context], list[dict] | None]:
    """Create a pipeline function that scans for contradiction arbs.

    For each (A, B) that are mutually exclusive: price_A + price_B <= 1.
    If sum > 1 + min_edge, there's an arb.

    Args:
        markets: List of market IDs to monitor.
        contradictions: List of (A, B) tuples that are mutually exclusive.
        min_edge: Minimum violation to flag.

    Returns:
        Pipeline function: (Context) -> list[dict] | None
    """

    def _scanner(ctx: Context) -> list[dict] | None:
        prices = ctx.params.get("market_prices", {})
        if not prices:
            for mid in markets:
                if mid in ctx.feeds:
                    prices[mid] = ctx.feeds[mid].price

        arbs: list[dict] = []
        for a, b in contradictions:
            pa = prices.get(a)
            pb = prices.get(b)
            if pa is None or pb is None:
                continue
            # Mutually exclusive: P(A) + P(B) <= 1
            total = pa + pb
            violation = total - 1.0
            if violation > min_edge:
                arbs.append({
                    "type": "contradiction",
                    "market_a": a,
                    "market_b": b,
                    "price_a": pa,
                    "price_b": pb,
                    "price_sum": total,
                    "edge": violation,
                    "action": f"sell both {a} and {b}",
                })

        if arbs:
            ctx.params["contradiction_arbs"] = arbs
            return arbs
        return None

    _scanner.__name__ = "contradiction_arb_scanner"
    return _scanner
