from fastapi import APIRouter, Depends
from pydantic import BaseModel
from typing import Any, Dict, List, Optional
import time

from app.broker.broker_manager import BrokerManager
from app.broker.broker_factory import BrokerFactory
from app.core.dependencies import get_current_user
from app.execution import idempotency, fill_tracker, position_guard


# ── Order request schemas ─────────────────────────────────────────

class PlaceOrderRequest(BaseModel):
    symbol: str
    token: str
    exchange: str                  # NSE / BSE / NFO / MCX / CDS
    transaction_type: str          # BUY / SELL
    order_type: str                # MARKET / LIMIT / SL / SL-M
    product_type: str              # INTRADAY / DELIVERY / CARRYFORWARD
    quantity: int
    price: float = 0
    trigger_price: float = 0
    variety: str = "NORMAL"        # NORMAL / STOPLOSS / AMO
    # PHASE-2 (Goal 3): optional idempotency key — a repeat request with the
    # same key replays the cached response instead of placing a second order.
    idempotency_key: Optional[str] = None


class ModifyOrderRequest(BaseModel):
    order_id: str
    variety: str = "NORMAL"
    order_type: Optional[str] = None
    quantity: Optional[int] = None
    price: Optional[float] = None
    trigger_price: Optional[float] = None


class CancelOrderRequest(BaseModel):
    order_id: str
    variety: str = "NORMAL"

router = APIRouter(
    prefix="/broker",
    tags=["Broker"]
)

manager = BrokerManager()


@router.post("/connect")
def connect_broker(
    broker: str,
    _user=Depends(get_current_user),
):
    return manager.connect(broker)


@router.post("/disconnect")
def disconnect_broker(_user=Depends(get_current_user)):
    return manager.disconnect()


@router.get("/profile")
def profile(_user=Depends(get_current_user)):

    broker = manager.get_broker()

    if broker is None:
        return {
            "success": False,
            "message": "Broker Not Connected"
        }

    return broker.profile()


@router.get("/funds")
def funds(_user=Depends(get_current_user)):

    broker = manager.get_broker()

    if broker is None:
        return {
            "success": False,
            "message": "Broker Not Connected"
        }

    return broker.funds()


@router.get("/holdings")
def holdings(_user=Depends(get_current_user)):

    broker = manager.get_broker()

    if broker is None:
        return {
            "success": False,
            "message": "Broker Not Connected"
        }

    return broker.holdings()


@router.get("/positions")
def positions(_user=Depends(get_current_user)):

    broker = manager.get_broker()

    if broker is None:
        return {
            "success": False,
            "message": "Broker Not Connected"
        }

    return broker.positions()


@router.get("/orders")
def orders(_user=Depends(get_current_user)):

    broker = manager.get_broker()

    if broker is None:
        return {
            "success": False,
            "message": "Broker Not Connected"
        }

    return broker.orders()


@router.get("/orders/{order_id}")
def get_order(order_id: str, _user=Depends(get_current_user)):
    """Get a single order by order ID."""

    broker = manager.get_broker()

    if broker is None:
        return {
            "success": False,
            "message": "Broker Not Connected"
        }

    result = broker.orders()

    # result may be a list or a dict with a data key
    order_list = result if isinstance(result, list) else result.get("data", [])

    for order in order_list:
        oid = str(order.get("orderid") or order.get("order_id") or "")
        if oid == order_id:
            return {"success": True, "data": order}

    return {"success": False, "message": f"Order {order_id} not found"}


@router.post("/orders/place")
def place_order(order: PlaceOrderRequest, _user=Depends(get_current_user)):
    """
    Place a live order via the connected broker.

    Body fields:
    - symbol       : Trading symbol  e.g. RELIANCE-EQ
    - token        : Instrument token (broker-specific)
    - exchange     : NSE / BSE / NFO / MCX / CDS
    - transaction_type : BUY or SELL
    - order_type   : MARKET / LIMIT / SL / SL-M
    - product_type : INTRADAY / DELIVERY / CARRYFORWARD
    - quantity     : Number of shares / lots
    - price        : Limit price (0 for MARKET)
    - trigger_price: Trigger price for SL orders (0 otherwise)
    - variety      : NORMAL / STOPLOSS / AMO  (default NORMAL)
    """

    broker_name = manager.get_broker_name() or "unknown"

    # ── PHASE-2 (Goals 1 & 3): idempotency / duplicate-order guard ─────────
    dedup_key = (
        idempotency.explicit_key(order.idempotency_key)
        if order.idempotency_key
        else idempotency.fingerprint(
            broker_name, order.symbol, order.exchange, order.transaction_type,
            order.order_type, order.product_type, order.quantity, order.price,
        )
    )
    cached = idempotency.get_cached(dedup_key)
    if cached is not None:
        return cached

    broker = manager.get_broker()

    if broker is None:
        response = {
            "success": False,
            "message": "Broker Not Connected",
            "fill_status": "REJECTED",
        }
        idempotency.store(dedup_key, response)
        return response

    # ── PHASE-2.5 (Goal 5): one-symbol-one-position guard ───────────────────
    existing_qty = position_guard.get_net_position_qty(broker, order.symbol, order.exchange)
    if position_guard.blocks_new_entry(existing_qty, order.transaction_type):
        response = {
            "success": False,
            "message": (
                f"{order.symbol} already has an open "
                f"{position_guard.describe_existing(existing_qty)} position — "
                f"one-symbol-one-position rule blocks another {order.transaction_type}. "
                f"Place an opposite-side order to reduce/close it instead."
            ),
            "status": "BLOCKED",
            "fill_status": "REJECTED",
        }
        idempotency.store(dedup_key, response)
        return response

    order_dict = {
        "symbol": order.symbol,
        "token": order.token,
        "exchange": order.exchange,
        "transactiontype": order.transaction_type,
        "ordertype": order.order_type,
        "producttype": order.product_type,
        "quantity": str(order.quantity),
        "price": str(order.price),
        "triggerprice": str(order.trigger_price),
        "variety": order.variety,
        "duration": "DAY",
        "tradingsymbol": order.symbol,
    }

    attempt_ts = time.time()
    response = broker.place_order(order_dict)
    order_id = str(response.get("order_id") or "")
    accepted = bool(response.get("success", False))

    # ── PHASE-2.5 (Goal 2): "accepted" != "filled" — poll for real fill state
    fill = None
    if accepted and order_id:
        fill = fill_tracker.poll_until_final(
            broker, order_id=order_id, symbol=order.symbol,
            side=order.transaction_type, quantity=order.quantity, since_ts=attempt_ts,
        )
    elif not accepted and fill_tracker.looks_ambiguous(response.get("message", "")):
        fill = fill_tracker.poll_until_final(
            broker, order_id="", symbol=order.symbol,
            side=order.transaction_type, quantity=order.quantity, since_ts=attempt_ts,
            max_wait_secs=8.0,
        )
        if fill.status != "UNKNOWN":
            order_id = fill.order_id or order_id
            accepted = fill.status in ("FILLED", "PARTIALLY_FILLED", "PENDING")

    response = dict(response)
    response["order_id"] = order_id
    response["success"] = accepted
    if fill is not None:
        response["fill_status"] = fill.status
        response["filled_quantity"] = fill.filled_qty
        response["avg_fill_price"] = fill.avg_price
    else:
        response.setdefault("fill_status", "REJECTED" if not accepted else "UNKNOWN")

    idempotency.store(dedup_key, response)
    return response


@router.post("/orders/modify")
def modify_order(order: ModifyOrderRequest, _user=Depends(get_current_user)):
    """
    Modify an existing pending order.

    Body fields:
    - order_id      : The broker order ID to modify
    - variety       : NORMAL / STOPLOSS / AMO
    - order_type    : New order type (optional)
    - quantity      : New quantity (optional)
    - price         : New limit price (optional)
    - trigger_price : New trigger price (optional)
    """

    broker = manager.get_broker()

    if broker is None:
        return {
            "success": False,
            "message": "Broker Not Connected"
        }

    modify_dict = {"orderid": order.order_id, "variety": order.variety}

    if order.order_type is not None:
        modify_dict["ordertype"] = order.order_type
    if order.quantity is not None:
        modify_dict["quantity"] = str(order.quantity)
    if order.price is not None:
        modify_dict["price"] = str(order.price)
    if order.trigger_price is not None:
        modify_dict["triggerprice"] = str(order.trigger_price)

    return broker.modify_order(order.order_id, modify_dict)


@router.post("/orders/cancel")
def cancel_order(order: CancelOrderRequest, _user=Depends(get_current_user)):
    """
    Cancel a pending order.

    Body fields:
    - order_id : The broker order ID to cancel
    - variety  : NORMAL / STOPLOSS / AMO  (default NORMAL)
    """

    broker = manager.get_broker()

    if broker is None:
        return {
            "success": False,
            "message": "Broker Not Connected"
        }

    return broker.cancel_order(order.order_id)


# ══════════════════════════════════════════════════════════════════
# PROMPT 1 – LIVE BROKER TESTING
# ══════════════════════════════════════════════════════════════════

SUPPORTED_BROKERS: List[str] = [
    "angelone", "aliceblue", "upstox", "zerodha",
    "fyers", "shoonya", "dhan", "mstock", "delta", "exness"
]


@router.get("/supported", summary="List all supported brokers")
def supported_brokers():
    """Returns all broker names accepted by the /broker/connect endpoint."""
    return {
        "success": True,
        "brokers": SUPPORTED_BROKERS,
        "count": len(SUPPORTED_BROKERS)
    }


def _run_test(label: str, fn) -> Dict[str, Any]:
    """Run a single broker test function and return a structured result."""
    start = time.time()
    try:
        data = fn()
        elapsed = round((time.time() - start) * 1000, 1)
        # Treat explicit success=False as failure
        if isinstance(data, dict) and data.get("success") is False:
            return {
                "status": "FAIL",
                "error": data.get("message", "Returned success=False"),
                "latency_ms": elapsed
            }
        return {"status": "PASS", "latency_ms": elapsed, "data": data}
    except Exception as exc:
        elapsed = round((time.time() - start) * 1000, 1)
        return {"status": "FAIL", "error": str(exc), "latency_ms": elapsed}


@router.post(
    "/test/{broker_name}",
    summary="Run full live broker test (connect → profile → funds → holdings → positions → orders)"
)
def test_broker(broker_name: str, _user=Depends(get_current_user)):
    """
    Creates a fresh broker instance, connects, then runs six capability tests:
    connect / profile / funds / holdings / positions / orders.

    Returns a structured test report with PASS / FAIL per test and an
    overall PASSED / FAILED / PARTIAL status.

    Supported broker names: angelone, aliceblue, upstox, zerodha,
    fyers, shoonya, dhan, mstock, delta, exness
    """

    report: Dict[str, Any] = {
        "broker": broker_name,
        "tests": {},
        "passed": 0,
        "failed": 0,
        "total": 0,
        "status": "UNKNOWN",
        "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
    }

    # ── Step 1: Instantiate broker ─────────────────────────────────
    try:
        broker = BrokerFactory.get_broker(broker_name)
    except Exception as exc:
        report["status"] = "FAILED"
        report["error"] = str(exc)
        report["hint"] = f"Supported: {', '.join(SUPPORTED_BROKERS)}"
        return report

    # ── Step 2: Connect ────────────────────────────────────────────
    connect_result = _run_test("connect", broker.connect)
    report["tests"]["connect"] = connect_result
    report["total"] += 1

    if connect_result["status"] == "FAIL":
        report["failed"] += 1
        report["status"] = "FAILED"
        report["hint"] = "Check broker credentials in .env file"
        return report

    report["passed"] += 1

    # ── Steps 3-7: Data endpoints ──────────────────────────────────
    capability_tests = [
        ("profile",   broker.profile),
        ("funds",     broker.funds),
        ("holdings",  broker.holdings),
        ("positions", broker.positions),
        ("orders",    broker.orders),
    ]

    for label, fn in capability_tests:
        result = _run_test(label, fn)
        report["tests"][label] = result
        report["total"] += 1
        if result["status"] == "PASS":
            report["passed"] += 1
        else:
            report["failed"] += 1

    # ── Overall status ─────────────────────────────────────────────
    if report["failed"] == 0:
        report["status"] = "PASSED"
    elif report["passed"] == 0:
        report["status"] = "FAILED"
    else:
        report["status"] = "PARTIAL"

    return report


@router.post(
    "/test",
    summary="Test currently connected broker (uses active BrokerManager session)"
)
def test_connected_broker(_user=Depends(get_current_user)):
    """
    Runs the full test suite against whichever broker is currently connected
    via POST /broker/connect.  Does NOT create a new connection.
    """
    broker = manager.get_broker()

    if broker is None:
        return {
            "success": False,
            "message": "No broker connected. Use POST /broker/connect first."
        }

    report: Dict[str, Any] = {
        "broker": manager.get_broker_name(),
        "tests": {},
        "passed": 0,
        "failed": 0,
        "total": 0,
        "status": "UNKNOWN",
        "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
    }

    for label, fn in [
        ("profile",   broker.profile),
        ("funds",     broker.funds),
        ("holdings",  broker.holdings),
        ("positions", broker.positions),
        ("orders",    broker.orders),
    ]:
        result = _run_test(label, fn)
        report["tests"][label] = result
        report["total"] += 1
        if result["status"] == "PASS":
            report["passed"] += 1
        else:
            report["failed"] += 1

    if report["failed"] == 0:
        report["status"] = "PASSED"
    elif report["passed"] == 0:
        report["status"] = "FAILED"
    else:
        report["status"] = "PARTIAL"

    return report


# ══════════════════════════════════════════════════════════════════
# PROMPT 2 – LIVE ORDER PLACEMENT  (Trade Book addition)
# ══════════════════════════════════════════════════════════════════

@router.get("/tradebook", summary="Get trade book (executed trades today)")
def tradebook(_user=Depends(get_current_user)):
    """
    Returns today's executed trade book from the connected broker.
    Supported by: Angel One (tradeBook), others via tradebook() if available.
    """
    broker = manager.get_broker()

    if broker is None:
        return {
            "success": False,
            "message": "Broker Not Connected"
        }

    # Check if broker has a tradebook() method (future-proof extension)
    if hasattr(broker, "tradebook"):
        return broker.tradebook()

    # AngelOne exposes tradeBook() via SmartAPI directly
    if hasattr(broker, "smart_api") and broker.smart_api is not None:
        try:
            return broker.smart_api.tradeBook()
        except Exception as exc:
            return {"success": False, "message": str(exc)}

    # Generic: other brokers that expose trade_book via attribute
    for attr in ("trade_book", "get_tradebook", "get_trade_book"):
        if hasattr(broker, attr):
            try:
                return getattr(broker, attr)()
            except Exception as exc:
                return {"success": False, "message": str(exc)}

    return {
        "success": False,
        "message": f"Trade book not yet implemented for this broker. "
                   f"Add a tradebook() method to its class."
    }