"""Algo execution client for algorithmic trading."""

from __future__ import annotations

from decimal import Decimal
from typing import Any, Dict, List, Optional

from zenotc.clients.base import BaseClient
from zenotc.models.algo import (
    AlgoOrder,
    AlgoOrderEvent,
    AlgoOrderStatus,
    AlgoType,
    ConditionalParams,
    CreateAlgoOrderRequest,
    IcebergParams,
    TriggerCondition,
    TwapParams,
    VolumeProfileEntry,
    VwapParams,
)
from zenotc.models.base import PaginatedResponse


class AlgoClient(BaseClient):
    """Client for algorithmic order execution.

    Supports TWAP, VWAP, Iceberg, and Conditional order types.
    """

    # ============ Core Methods ============

    async def create_order(
        self,
        asset: str,
        side: str,
        algo_type: str,
        twap_params: Optional[Dict[str, Any]] = None,
        vwap_params: Optional[Dict[str, Any]] = None,
        iceberg_params: Optional[Dict[str, Any]] = None,
        conditional_params: Optional[Dict[str, Any]] = None,
        metadata: Optional[Dict[str, Any]] = None,
    ) -> AlgoOrder:
        """Create a new algo order.

        The order is created in 'pending' status and must be started separately.

        Args:
            asset: Asset symbol (e.g., 'BTC')
            side: Order side ('buy' or 'sell')
            algo_type: Algorithm type ('twap', 'vwap', 'iceberg', 'conditional')
            twap_params: TWAP-specific parameters
            vwap_params: VWAP-specific parameters
            iceberg_params: Iceberg-specific parameters
            conditional_params: Conditional-specific parameters
            metadata: Optional metadata

        Returns:
            The created algo order
        """
        data: Dict[str, Any] = {
            "asset": asset.upper(),
            "side": side.lower(),
            "algoType": algo_type.lower(),
        }

        if twap_params:
            data["twapParams"] = twap_params
        if vwap_params:
            data["vwapParams"] = vwap_params
        if iceberg_params:
            data["icebergParams"] = iceberg_params
        if conditional_params:
            data["conditionalParams"] = conditional_params
        if metadata:
            data["metadata"] = metadata

        await self._rate_limiter.acquire()
        response = await self._http.post("/api/sdk/v1/algo/orders", json_data=data)
        return AlgoOrder.model_validate(response)

    async def get_order(self, order_id: str) -> AlgoOrder:
        """Get an algo order by ID.

        Args:
            order_id: The algo order ID

        Returns:
            The algo order
        """
        await self._rate_limiter.acquire()
        response = await self._http.get(f"/api/sdk/v1/algo/orders/{order_id}")
        return AlgoOrder.model_validate(response)

    async def list_orders(
        self,
        status: Optional[str] = None,
        algo_type: Optional[str] = None,
        asset: Optional[str] = None,
        page: int = 1,
        limit: int = 50,
    ) -> PaginatedResponse[AlgoOrder]:
        """List algo orders with optional filtering.

        Args:
            status: Filter by status
            algo_type: Filter by algorithm type
            asset: Filter by asset
            page: Page number
            limit: Results per page

        Returns:
            Paginated list of algo orders
        """
        params: Dict[str, Any] = {"page": page, "limit": limit}

        if status:
            params["status"] = status
        if algo_type:
            params["algoType"] = algo_type
        if asset:
            params["asset"] = asset.upper()

        await self._rate_limiter.acquire()
        response = await self._http.get("/api/sdk/v1/algo/orders", params=params)

        orders = [AlgoOrder.model_validate(o) for o in response.get("data", [])]
        return PaginatedResponse[AlgoOrder](
            data=orders,
            total=response.get("total", len(orders)),
            page=response.get("page", page),
            limit=response.get("limit", limit),
            has_more=response.get("hasMore", False),
        )

    async def start_order(self, order_id: str) -> AlgoOrder:
        """Start executing an algo order.

        The order must be in 'pending' or 'paused' status.

        Args:
            order_id: The algo order ID

        Returns:
            The updated algo order
        """
        await self._rate_limiter.acquire()
        response = await self._http.post(f"/api/sdk/v1/algo/orders/{order_id}/start")
        return AlgoOrder.model_validate(response)

    async def pause_order(self, order_id: str) -> AlgoOrder:
        """Pause an executing algo order.

        The order must be in 'running' status.

        Args:
            order_id: The algo order ID

        Returns:
            The updated algo order
        """
        await self._rate_limiter.acquire()
        response = await self._http.post(f"/api/sdk/v1/algo/orders/{order_id}/pause")
        return AlgoOrder.model_validate(response)

    async def resume_order(self, order_id: str) -> AlgoOrder:
        """Resume a paused algo order.

        The order must be in 'paused' status.

        Args:
            order_id: The algo order ID

        Returns:
            The updated algo order
        """
        await self._rate_limiter.acquire()
        response = await self._http.post(f"/api/sdk/v1/algo/orders/{order_id}/resume")
        return AlgoOrder.model_validate(response)

    async def cancel_order(self, order_id: str) -> AlgoOrder:
        """Cancel an algo order.

        Any remaining unfilled quantity will be cancelled.

        Args:
            order_id: The algo order ID

        Returns:
            The cancelled algo order
        """
        await self._rate_limiter.acquire()
        response = await self._http.delete(f"/api/sdk/v1/algo/orders/{order_id}")
        return AlgoOrder.model_validate(response)

    async def get_order_events(
        self,
        order_id: str,
        limit: int = 100,
    ) -> List[AlgoOrderEvent]:
        """Get execution events for an algo order.

        Events include slice executions, status changes, and errors.

        Args:
            order_id: The algo order ID
            limit: Maximum number of events to return

        Returns:
            List of events
        """
        params = {"limit": limit} if limit else {}

        await self._rate_limiter.acquire()
        response = await self._http.get(
            f"/api/sdk/v1/algo/orders/{order_id}/events",
            params=params,
        )

        if isinstance(response, list):
            return [AlgoOrderEvent.model_validate(e) for e in response]
        return [AlgoOrderEvent.model_validate(e) for e in response.get("data", [])]

    # ============ Convenience Methods ============

    async def create_twap(
        self,
        asset: str,
        side: str,
        total_quantity: float,
        duration_seconds: int,
        slice_count: int,
        jitter_percent: Optional[float] = None,
        min_slice_size: Optional[float] = None,
        price_limit: Optional[float] = None,
        metadata: Optional[Dict[str, Any]] = None,
        auto_start: bool = True,
    ) -> AlgoOrder:
        """Create a TWAP (Time-Weighted Average Price) order.

        Executes a large order by splitting it into equal-sized slices
        distributed evenly over a time period.

        Args:
            asset: Asset symbol
            side: 'buy' or 'sell'
            total_quantity: Total quantity to execute
            duration_seconds: Duration over which to execute
            slice_count: Number of slices
            jitter_percent: Random timing variance (0-50%)
            min_slice_size: Minimum slice size
            price_limit: Maximum/minimum price limit
            metadata: Optional metadata
            auto_start: Whether to start immediately (default True)

        Returns:
            The algo order
        """
        twap_params: Dict[str, Any] = {
            "totalQuantity": total_quantity,
            "durationSeconds": duration_seconds,
            "sliceCount": slice_count,
        }

        if jitter_percent is not None:
            twap_params["jitterPercent"] = jitter_percent
        if min_slice_size is not None:
            twap_params["minSliceSize"] = min_slice_size
        if price_limit is not None:
            twap_params["priceLimit"] = price_limit

        order = await self.create_order(
            asset=asset,
            side=side,
            algo_type="twap",
            twap_params=twap_params,
            metadata=metadata,
        )

        if auto_start:
            order = await self.start_order(order.id)

        return order

    async def create_vwap(
        self,
        asset: str,
        side: str,
        total_quantity: float,
        duration_seconds: int,
        participation_rate: Optional[float] = None,
        max_deviation_percent: Optional[float] = None,
        volume_profile: Optional[List[Dict[str, Any]]] = None,
        price_limit: Optional[float] = None,
        metadata: Optional[Dict[str, Any]] = None,
        auto_start: bool = True,
    ) -> AlgoOrder:
        """Create a VWAP (Volume-Weighted Average Price) order.

        Executes a large order by distributing slices according to
        historical volume patterns.

        Args:
            asset: Asset symbol
            side: 'buy' or 'sell'
            total_quantity: Total quantity to execute
            duration_seconds: Duration over which to execute
            participation_rate: Max percentage of volume to participate (0-1)
            max_deviation_percent: Maximum deviation from VWAP
            volume_profile: Custom volume profile
            price_limit: Maximum/minimum price limit
            metadata: Optional metadata
            auto_start: Whether to start immediately (default True)

        Returns:
            The algo order
        """
        vwap_params: Dict[str, Any] = {
            "totalQuantity": total_quantity,
            "durationSeconds": duration_seconds,
        }

        if participation_rate is not None:
            vwap_params["participationRate"] = participation_rate
        if max_deviation_percent is not None:
            vwap_params["maxDeviationPercent"] = max_deviation_percent
        if volume_profile is not None:
            vwap_params["volumeProfile"] = volume_profile
        if price_limit is not None:
            vwap_params["priceLimit"] = price_limit

        order = await self.create_order(
            asset=asset,
            side=side,
            algo_type="vwap",
            vwap_params=vwap_params,
            metadata=metadata,
        )

        if auto_start:
            order = await self.start_order(order.id)

        return order

    async def create_iceberg(
        self,
        asset: str,
        side: str,
        total_quantity: float,
        display_quantity: float,
        variance: Optional[float] = None,
        refresh_delay_seconds: Optional[int] = None,
        price_limit: Optional[float] = None,
        price_limit_type: Optional[str] = None,
        trailing_offset: Optional[float] = None,
        metadata: Optional[Dict[str, Any]] = None,
        auto_start: bool = True,
    ) -> AlgoOrder:
        """Create an Iceberg order.

        Executes a large order by showing only a small 'display' quantity
        at a time, hiding the full order size.

        Args:
            asset: Asset symbol
            side: 'buy' or 'sell'
            total_quantity: Total hidden quantity to execute
            display_quantity: Visible quantity per leg
            variance: Random variance for display quantity (0-1)
            refresh_delay_seconds: Delay between leg refreshes
            price_limit: Price limit
            price_limit_type: 'fixed', 'relative', or 'trailing'
            trailing_offset: Offset for trailing limit
            metadata: Optional metadata
            auto_start: Whether to start immediately (default True)

        Returns:
            The algo order
        """
        iceberg_params: Dict[str, Any] = {
            "totalQuantity": total_quantity,
            "displayQuantity": display_quantity,
        }

        if variance is not None:
            iceberg_params["variance"] = variance
        if refresh_delay_seconds is not None:
            iceberg_params["refreshDelaySeconds"] = refresh_delay_seconds
        if price_limit is not None:
            iceberg_params["priceLimit"] = price_limit
        if price_limit_type is not None:
            iceberg_params["priceLimitType"] = price_limit_type
        if trailing_offset is not None:
            iceberg_params["trailingOffset"] = trailing_offset

        order = await self.create_order(
            asset=asset,
            side=side,
            algo_type="iceberg",
            iceberg_params=iceberg_params,
            metadata=metadata,
        )

        if auto_start:
            order = await self.start_order(order.id)

        return order

    async def create_conditional(
        self,
        asset: str,
        side: str,
        trigger_condition: str,
        trigger_price: float,
        quantity: float,
        order_type: str = "market",
        limit_price: Optional[float] = None,
        expires_at: Optional[str] = None,
        reference_asset: Optional[str] = None,
        metadata: Optional[Dict[str, Any]] = None,
        auto_start: bool = True,
    ) -> AlgoOrder:
        """Create a Conditional order.

        Creates an order that triggers when a price condition is met.

        Args:
            asset: Asset symbol
            side: 'buy' or 'sell'
            trigger_condition: 'price_above', 'price_below', or 'price_cross'
            trigger_price: Price that triggers the order
            quantity: Quantity to execute when triggered
            order_type: 'market' or 'limit'
            limit_price: Limit price (required if order_type is 'limit')
            expires_at: Expiration time (ISO 8601)
            reference_asset: Asset to monitor (defaults to order asset)
            metadata: Optional metadata
            auto_start: Whether to start immediately (default True)

        Returns:
            The algo order
        """
        conditional_params: Dict[str, Any] = {
            "triggerCondition": trigger_condition,
            "triggerPrice": trigger_price,
            "quantity": quantity,
            "orderType": order_type,
        }

        if limit_price is not None:
            conditional_params["limitPrice"] = limit_price
        if expires_at is not None:
            conditional_params["expiresAt"] = expires_at
        if reference_asset is not None:
            conditional_params["referenceAsset"] = reference_asset

        order = await self.create_order(
            asset=asset,
            side=side,
            algo_type="conditional",
            conditional_params=conditional_params,
            metadata=metadata,
        )

        if auto_start:
            order = await self.start_order(order.id)

        return order

    async def get_running_orders(self) -> List[AlgoOrder]:
        """Get all currently running algo orders.

        Returns:
            List of running algo orders
        """
        result = await self.list_orders(status="running", limit=100)
        return result.data

    async def cancel_all_running(self) -> List[AlgoOrder]:
        """Cancel all running algo orders.

        Returns:
            List of cancelled algo orders
        """
        running = await self.get_running_orders()
        cancelled = []
        for order in running:
            try:
                cancelled_order = await self.cancel_order(order.id)
                cancelled.append(cancelled_order)
            except Exception:
                pass  # Continue cancelling others even if one fails
        return cancelled
