from __future__ import annotations

from models.companion_rules import TradeZone


def _instrument_matches(zone_instrument: str | None, ticker: str | None) -> bool:
    if not zone_instrument or not ticker:
        return True
    left = zone_instrument.strip().lower()
    right = ticker.strip().lower()
    return left == right or left in right or right in left


def _zone_price_match(price: float, zone: TradeZone) -> bool:
    low, high = sorted((zone.low, zone.high))
    return low <= price <= high


def _normalize_action(action: str | None) -> str | None:
    if not action:
        return None
    value = action.strip().lower()
    if value in ("buy", "long", "achat", "buytocover"):
        return "buy"
    if value in ("sell", "short", "vente", "sellshort"):
        return "sell"
    return value


def _zone_direction(zone: TradeZone) -> str:
    return (zone.direction or "").strip().lower()


def _action_matches_zone_direction(action: str | None, zone: TradeZone) -> bool:
    direction = _zone_direction(zone)
    if direction not in ("buy", "sell"):
        return True
    normalized = _normalize_action(action)
    if normalized is None:
        return True
    return normalized == direction


def _zones_at_price(
    price: float,
    zones: list[TradeZone],
    *,
    ticker: str | None = None,
) -> list[TradeZone]:
    matched: list[TradeZone] = []
    for zone in zones:
        if not _instrument_matches(zone.instrument, ticker):
            continue
        if _zone_price_match(price, zone):
            matched.append(zone)
    return matched


def _applicable_zones(at_price: list[TradeZone]) -> list[TradeZone]:
    directed = [z for z in at_price if _zone_direction(z) in ("buy", "sell")]
    return directed if directed else at_price


def price_in_trade_zones(
    price: float,
    zones: list[TradeZone],
    *,
    ticker: str | None = None,
    action: str | None = None,
) -> bool:
    at_price = _zones_at_price(price, zones, ticker=ticker)
    if not at_price:
        return False
    applicable = _applicable_zones(at_price)
    normalized = _normalize_action(action)
    if normalized not in ("buy", "sell"):
        return True
    return any(_action_matches_zone_direction(normalized, zone) for zone in applicable)


def trade_zone_status(
    *,
    enabled: bool,
    zones: list[TradeZone],
    price: float | None,
    ticker: str | None = None,
    action: str | None = None,
) -> tuple[bool, str | None]:
    if not enabled:
        return True, None
    if not zones:
        return False, "Trade zone required — draw a zone on the chart"
    if price is None:
        return False, "Live price required — open Ninja with the active chart to send the price"

    at_price = _zones_at_price(price, zones, ticker=ticker)
    if not at_price:
        return False, f"Price {price:.2f} outside trade zone"

    applicable = _applicable_zones(at_price)
    normalized = _normalize_action(action)
    if normalized not in ("buy", "sell"):
        return True, None

    if any(_action_matches_zone_direction(normalized, zone) for zone in applicable):
        return True, None

    if normalized == "buy":
        return False, "Sell-only zone — buys not allowed in this zone"
    return False, "Buy-only zone — sells not allowed in this zone"
