from typing import Any

from connectors.base import PlatformConnector
from core.engine import discipline_engine
from models.companion_rules import CompanionRulesUpdate, TradeZone
from models.platform import (
    ConnectorStatus,
    IntegrationPhase,
    PlatformId,
    TradeIntent,
)
from services.ninja_presence import mark_ninja_seen, ninja_is_connected
from services.runtime_rules import set_live_price
from services.stop_loss_registry import remove_position, sync_positions
from services.trade_ledger import record_event
from services.user_settings import apply_companion_rules


def _message_type(payload: dict[str, Any]) -> str:
    raw = payload.get("type") or payload.get("msg_type") or payload.get("kind")
    if raw is None:
        return "check"
    return str(raw).strip().lower()


def _normalize_side(action: str | None) -> str:
    value = (action or "buy").strip().lower()
    if value in ("sell", "short"):
        return "sell"
    if value in ("close", "exit", "flat"):
        return "close"
    return "buy"


def _normalize_event(payload: dict[str, Any]) -> str:
    explicit = payload.get("event")
    if explicit is not None:
        value = str(explicit).strip().lower()
        if value in ("open", "close"):
            return value
    action = _normalize_side(str(payload.get("action") or payload.get("side") or "buy"))
    return "close" if action == "close" else "open"


class NinjaTraderConnector(PlatformConnector):
    platform_id = PlatformId.NINJA
    display_name = "NinjaTrader"

    def status(self) -> ConnectorStatus:
        connected = ninja_is_connected()
        message = (
            "NT8 addon connected — check + fills active"
            if connected
            else "NT8 addon — waiting for connection (ping)"
        )
        return ConnectorStatus(
            platform=self.platform_id,
            name=self.display_name,
            phase=IntegrationPhase.DISCIPLINE,
            enabled=True,
            connected=connected,
            discipline_manual=True,
            discipline_auto=True,
            message=message,
        )

    async def handle_inbound(self, payload: dict[str, Any]) -> dict[str, Any]:
        msg_type = _message_type(payload)

        if msg_type in ("ping", "heartbeat"):
            mark_ninja_seen()
            return {
                "ok": True,
                "type": "ping",
                "platform": self.platform_id.value,
                "connected": True,
                "message": "Paychek Safeguard OK",
            }

        if msg_type == "check":
            mark_ninja_seen()
            from services.license_service import is_license_active

            if not is_license_active():
                return {
                    "ok": True,
                    "type": "check",
                    "platform": self.platform_id.value,
                    "allowed": False,
                    "reason": "License not activated — open Paychek and enter your license key.",
                    "message": "License not activated — open Paychek and enter your license key.",
                    "license_activated": False,
                }
            return self._handle_check(payload)

        if msg_type in ("fill", "execution", "trade"):
            mark_ninja_seen()
            return self._handle_fill(payload)

        if msg_type in ("zone_sync", "zone", "zones"):
            mark_ninja_seen()
            return self._handle_zone_sync(payload)

        if msg_type in ("live_price", "price"):
            mark_ninja_seen()
            return self._handle_live_price(payload)

        if msg_type in ("sl_sync", "stop_loss_sync", "sl"):
            mark_ninja_seen()
            return self._handle_sl_sync(payload)

        return {
            "ok": False,
            "platform": self.platform_id.value,
            "error": "unknown_type",
            "message": f"Unknown type: {msg_type}. Use check, fill, zone_sync, sl_sync, or ping.",
        }

    def _handle_check(self, payload: dict[str, Any]) -> dict[str, Any]:
        ticker = str(payload.get("ticker") or payload.get("instrument") or "UNKNOWN")
        mark_price = _to_float(
            payload.get("mark_price") or payload.get("market_price") or payload.get("live_price")
        )
        if mark_price is not None and mark_price > 0:
            set_live_price(ticker, mark_price)

        intent = TradeIntent(
            platform=PlatformId.NINJA,
            ticker=ticker,
            action=str(payload.get("action") or payload.get("side") or "buy"),
            source=str(payload.get("source") or "ninja_addon"),
            strategy=payload.get("strategy"),
            price=_to_float(payload.get("price")),
            quantity=_to_float(payload.get("quantity") or payload.get("qty")),
            stop_loss=_to_float(payload.get("stop_loss") or payload.get("stop")),
            raw=payload,
        )
        decision = discipline_engine.check_intent(intent)
        snapshot = discipline_engine.snapshot()
        trades_remaining = snapshot.get("trades_remaining")
        return {
            "ok": True,
            "type": "check",
            "platform": self.platform_id.value,
            "allowed": decision.allowed,
            "reason": decision.reason,
            "message": "OK — trading allowed" if decision.allowed else decision.reason,
            "discipline_engaged": snapshot.get("discipline_engaged"),
            "max_lot_enabled": snapshot.get("max_lot_enabled"),
            "trade_interval_enabled": snapshot.get("trade_interval_enabled"),
            "max_risk_per_trade_enabled": snapshot.get("max_risk_per_trade_enabled"),
            "trades_opened_in_session": snapshot.get("trades_opened_in_session"),
            "trades_remaining": trades_remaining,
            "max_trades_per_session": snapshot.get("max_trades_per_session"),
            "within_session": snapshot.get("within_session"),
            "trade_zones_enabled": snapshot.get("trade_zones_enabled"),
            "price_in_trade_zone": snapshot.get("price_in_trade_zone"),
        }

    def _handle_sl_sync(self, payload: dict[str, Any]) -> dict[str, Any]:
        positions_raw = payload.get("positions")
        if not isinstance(positions_raw, list):
            positions_raw = [payload] if payload.get("ticker") or payload.get("instrument") else []

        source = str(payload.get("source") or "ninja_addon")
        synced = sync_positions(positions_raw, source=source)
        snapshot = discipline_engine.snapshot()
        max_mod = snapshot.get("sl_modify_max_count")
        limit_on = snapshot.get("sl_modify_limit_enabled")
        rows = []
        for row in synced:
            item = dict(row)
            if limit_on:
                item["sl_modify_max"] = max_mod
            rows.append(item)

        return {
            "ok": True,
            "type": "sl_sync",
            "platform": self.platform_id.value,
            "stop_loss_positions": rows,
            "stop_loss_positions_count": len(rows),
        }

    def _handle_zone_sync(self, payload: dict[str, Any]) -> dict[str, Any]:
        zones_raw = payload.get("zones")
        enabled = payload.get("enabled")
        instrument = _optional_str(payload.get("instrument") or payload.get("ticker"))
        zones: list[TradeZone] | None = None

        if isinstance(zones_raw, list):
            parsed: list[TradeZone] = []
            for index, item in enumerate(zones_raw):
                if not isinstance(item, dict):
                    continue
                low = _to_float(item.get("low"))
                high = _to_float(item.get("high"))
                if low is None or high is None:
                    continue
                zone_id = _optional_str(item.get("id")) or f"ninja_zone_{index + 1}"
                parsed.append(
                    TradeZone(
                        id=zone_id,
                        low=low,
                        high=high,
                        instrument=_optional_str(item.get("instrument")) or instrument,
                        label=_optional_str(item.get("label")),
                        side=str(item.get("side") or ""),
                        direction=str(item.get("direction") or ""),
                        source=str(item.get("source") or "addon"),
                    )
                )
            zones = parsed

        if zones is None and instrument and payload.get("low") is not None and payload.get("high") is not None:
            low = _to_float(payload.get("low"))
            high = _to_float(payload.get("high"))
            if low is not None and high is not None:
                zones = [
                    TradeZone(
                        id=_optional_str(payload.get("id")) or "ninja_zone_1",
                        low=low,
                        high=high,
                        instrument=instrument,
                        label=_optional_str(payload.get("label")) or "Ninja zone",
                        source="addon",
                    )
                ]

        update_kwargs: dict[str, Any] = {}
        if enabled is not None:
            update_kwargs["trade_zones_enabled"] = bool(enabled)
        if zones is not None:
            update_kwargs["trade_zones"] = zones
        if not update_kwargs:
            return {
                "ok": False,
                "type": "zone_sync",
                "platform": self.platform_id.value,
                "error": "empty_payload",
            }

        response = apply_companion_rules(CompanionRulesUpdate(**update_kwargs), skip_zone_edit_lock=True)
        snapshot = discipline_engine.snapshot()
        return {
            "ok": True,
            "type": "zone_sync",
            "platform": self.platform_id.value,
            "trade_zones_enabled": response.get("trade_zones_enabled"),
            "trade_zones_count": len(response.get("trade_zones") or []),
            "trade_zones": response.get("trade_zones") or [],
            "price_in_trade_zone": snapshot.get("price_in_trade_zone"),
        }

    def _handle_live_price(self, payload: dict[str, Any]) -> dict[str, Any]:
        price = _to_float(payload.get("price"))
        if price is None:
            return {
                "ok": False,
                "type": "live_price",
                "platform": self.platform_id.value,
                "error": "missing_price",
            }
        instrument = str(payload.get("ticker") or payload.get("instrument") or "")
        set_live_price(instrument, price)
        snapshot = discipline_engine.snapshot()
        return {
            "ok": True,
            "type": "live_price",
            "platform": self.platform_id.value,
            "live_price": price,
            "price_in_trade_zone": snapshot.get("price_in_trade_zone"),
        }

    def _handle_fill(self, payload: dict[str, Any]) -> dict[str, Any]:
        event = _normalize_event(payload)
        ticker = str(payload.get("ticker") or payload.get("instrument") or "UNKNOWN")
        side = _normalize_side(str(payload.get("action") or payload.get("side") or "buy"))
        quantity = _to_float(payload.get("quantity") or payload.get("qty")) or 1.0
        price = _to_float(payload.get("price") or payload.get("fill_price")) or 0.0
        pnl = _to_float(payload.get("pnl"))

        row = record_event(
            event=event,
            ticker=ticker,
            side=side if side != "close" else str(payload.get("fill_side") or "close"),
            quantity=quantity,
            price=price,
            source="ninjatrader",
            pnl=pnl if event == "close" else None,
            external_id=_optional_str(payload.get("order_id") or payload.get("execution_id")),
            account=_optional_str(payload.get("account")),
        )

        if event == "close":
            remove_position(
                account=_optional_str(payload.get("account")),
                ticker=ticker,
            )

        snapshot = discipline_engine.snapshot()
        return {
            "ok": True,
            "type": "fill",
            "platform": self.platform_id.value,
            "recorded": row,
            "trades_opened_in_session": snapshot.get("trades_opened_in_session"),
            "trades_remaining": snapshot.get("trades_remaining"),
        }

    def parse_intent(self, payload: dict[str, Any]) -> TradeIntent | None:
        if _message_type(payload) in (
            "ping",
            "fill",
            "execution",
            "trade",
            "zone_sync",
            "zone",
            "zones",
            "live_price",
            "price",
            "sl_sync",
            "stop_loss_sync",
        ):
            return None
        return TradeIntent(
            platform=PlatformId.NINJA,
            ticker=str(payload.get("ticker") or payload.get("instrument") or "UNKNOWN"),
            action=str(payload.get("action") or payload.get("side") or "buy"),
            source=str(payload.get("source") or "ninja_addon"),
            strategy=payload.get("strategy"),
            price=_to_float(payload.get("price")),
            quantity=_to_float(payload.get("quantity") or payload.get("qty")),
            stop_loss=_to_float(payload.get("stop_loss") or payload.get("stop")),
            raw=payload,
        )


def _to_float(value: Any) -> float | None:
    if value is None or value == "":
        return None
    try:
        return float(value)
    except (TypeError, ValueError):
        return None


def _optional_str(value: Any) -> str | None:
    if value is None:
        return None
    text = str(value).strip()
    return text or None
