"""Polyhush API Client"""

import os
import requests
from typing import Dict, List, Any, Optional


class PolyhushAPIError(Exception):
    """Exception for API errors"""
    def __init__(self, message: str, status_code: int = None, response: Dict = None):
        self.message = message
        self.status_code = status_code
        self.response = response
        super().__init__(self.message)


class PolyhushClient:
    """
    Polyhush trading client.
    
    Usage:
        client = PolyhushClient(api_key="your-api-key")
        client.get_balance()
        client.buy(token_id="...", shares=10, price=0.5)
    """
    
    DEFAULT_URL = "https://api.polyhush.com"
    
    # Order type constants
    GTC = "GTC"  # Good Till Cancelled - limit order, sits on book
    FOK = "FOK"  # Fill Or Kill - must fully fill immediately or cancel
    FAK = "FAK"  # Fill And Kill - market order, fills immediately, cancels unfilled
    
    def __init__(self, api_key: str = None, base_url: str = None):
        """
        Initialize the client.
        
        Args:
            api_key: Your Polyhush API key (or set POLYHUSH_API_KEY env var)
            base_url: API base URL (defaults to production)
        """
        self.api_key = api_key or os.environ.get("POLYHUSH_API_KEY")
        self.base_url = (base_url or self.DEFAULT_URL).rstrip("/")
        
        if not self.api_key:
            raise ValueError("API key required. Pass api_key or set POLYHUSH_API_KEY env var.")
    
    def _headers(self) -> Dict[str, str]:
        return {
            "Content-Type": "application/json",
            "X-API-Key": self.api_key
        }
    
    def _request(self, method: str, endpoint: str, data: Dict = None) -> Any:
        url = f"{self.base_url}{endpoint}"
        
        try:
            if method == "GET":
                resp = requests.get(url, headers=self._headers(), params=data)
            elif method == "POST":
                resp = requests.post(url, headers=self._headers(), json=data)
            elif method == "DELETE":
                resp = requests.delete(url, headers=self._headers())
            else:
                raise ValueError(f"Unsupported method: {method}")
            
            if resp.status_code >= 400:
                try:
                    error_data = resp.json()
                    msg = error_data.get("detail", resp.text)
                except:
                    msg = resp.text
                    error_data = None
                raise PolyhushAPIError(msg, resp.status_code, error_data)
            
            return resp.json() if resp.content else {}
            
        except requests.exceptions.RequestException as e:
            raise PolyhushAPIError(f"Request failed: {e}")
    
    # ─── Balance & Account ──────────────────────────────────────────────────
    
    def get_balance(self, include_summary: bool = False) -> Dict:
        """
        Get your USDC balance.
        
        Args:
            include_summary: If True, includes portfolio totals (position value, 
                           order counts). Default False for just balance info.
        
        Returns:
            Dict with balance, available_balance, reserved_balance.
            When include_summary=True, also includes:
            - total_position_value: Current market value of all positions
            - total_value: Balance + position value
            - open_orders_count: Number of open orders
            - positions_count: Number of active positions
        
        Examples:
            # Just get balance
            balance = client.get_balance()
            print(f"Available: ${balance['available_balance']}")
            
            # Get full portfolio summary
            summary = client.get_balance(include_summary=True)
            print(f"Total value: ${summary['total_value']}")
        """
        params = {"include_summary": str(include_summary).lower()} if include_summary else None
        return self._request("GET", "/api/v1/trading/balance", params)
    
    # ─── Positions ──────────────────────────────────────────────────────────
    
    def get_positions(self, detailed: bool = False) -> List[Dict]:
        """
        Get your current positions.
        
        Args:
            detailed: If True, returns enriched positions with current prices,
                     unrealized P&L, and market info. Default False for basic data.
        
        Returns:
            List of positions with market_id, token_id, side, size, average_price.
            When detailed=True, also includes:
            - current_price: Current market price
            - unrealized_pnl: Unrealized profit/loss in USDC
            - unrealized_pnl_pct: Unrealized P&L as percentage
            - market_value: Current position value
            - cost_basis: Original cost of position
            - market_question: The market question text
            - outcome_name: Name of the outcome you hold
            - reserved_for_sell: Shares reserved in pending sell orders
            - available_to_sell: Shares available to sell
        
        Examples:
            # Basic positions
            positions = client.get_positions()
            
            # Detailed with P&L
            positions = client.get_positions(detailed=True)
            for pos in positions:
                print(f"{pos['market_question']}: {pos['unrealized_pnl_pct']:.1f}%")
        """
        params = {"detailed": str(detailed).lower()} if detailed else None
        return self._request("GET", "/api/v1/trading/positions", params)
    
    def get_clob_balance(self, token_id: str) -> Dict:
        """
        Get the actual CLOB balance for a specific token.
        
        This returns the real on-chain/CLOB balance, which may differ from
        the database-tracked position. Use this to verify what's actually
        available to sell on Polymarket.
        
        Args:
            token_id: The outcome token ID
        
        Returns:
            Dict with clob_balance (shares), raw_balance, and allowances
        """
        return self._request("GET", f"/api/v1/trading/positions/{token_id}/clob-balance")
    
    # ─── Orders ─────────────────────────────────────────────────────────────
    
    def buy(
        self, 
        token_id: str, 
        shares: float = None, 
        price: float = None,
        usdc_amount: float = None,
        order_type: str = "GTC"
    ) -> Dict:
        """
        Place a buy order.
        
        For limit orders (GTC): provide shares + price
        For market orders (FOK/FAK): provide usdc_amount OR shares
        
        Args:
            token_id: The outcome token ID
            shares: Number of shares to buy (required for GTC, optional for market)
            price: Limit price per share 0.01-0.99 (required for GTC, ignored for market)
            usdc_amount: USDC to spend (for market orders only, preferred over shares)
            order_type: GTC (limit), FOK (fill or kill), FAK (market). Default GTC.
        
        Returns:
            Dict with order_id, status, message, warning (if any), latency_ms
        
        Examples:
            # Limit order: buy 100 shares at $0.45
            client.buy(token_id, shares=100, price=0.45)
            
            # Market order: spend $50 at market price
            client.buy(token_id, usdc_amount=50, order_type="FAK")
            
            # Market order: buy 100 shares at market price
            client.buy(token_id, shares=100, order_type="FAK")
        """
        order_type = order_type.upper()
        is_market_order = order_type in ["FOK", "FAK"]
        
        # Validation
        if not is_market_order:
            # Limit order requires shares + price
            if shares is None:
                raise ValueError("Limit orders require 'shares' parameter")
            if price is None:
                raise ValueError("Limit orders require 'price' parameter")
        else:
            # Market order requires at least one of shares or usdc_amount
            if shares is None and usdc_amount is None:
                raise ValueError("Market orders require either 'shares' or 'usdc_amount'")
        
        payload = {
            "token_id": token_id,
            "side": "BUY",
            "order_type": order_type,
        }
        
        if shares is not None:
            payload["shares"] = shares
        if price is not None:
            payload["price"] = price
        if usdc_amount is not None:
            payload["usdc_amount"] = usdc_amount
            
        return self._request("POST", "/api/v1/trading/orders", payload)
    
    def sell(
        self, 
        token_id: str, 
        shares: float = None, 
        price: float = None,
        usdc_amount: float = None,
        order_type: str = "GTC"
    ) -> Dict:
        """
        Place a sell order.
        
        For limit orders (GTC): provide shares + price
        For market orders (FOK/FAK): provide shares OR usdc_amount
        
        Args:
            token_id: The outcome token ID
            shares: Number of shares to sell (required for GTC, preferred for market)
            price: Limit price per share 0.01-0.99 (required for GTC, ignored for market)
            usdc_amount: USDC worth of shares to sell (for market orders, converted at market price)
            order_type: GTC (limit), FOK (fill or kill), FAK (market). Default GTC.
        
        Returns:
            Dict with order_id, status, message, warning (if any), latency_ms
        
        Examples:
            # Limit order: sell 100 shares at $0.55
            client.sell(token_id, shares=100, price=0.55)
            
            # Market order: sell 50 shares at market price
            client.sell(token_id, shares=50, order_type="FAK")
            
            # Market order: sell $100 worth at market price
            client.sell(token_id, usdc_amount=100, order_type="FAK")
        """
        order_type = order_type.upper()
        is_market_order = order_type in ["FOK", "FAK"]
        
        # Validation
        if not is_market_order:
            # Limit order requires shares + price
            if shares is None:
                raise ValueError("Limit orders require 'shares' parameter")
            if price is None:
                raise ValueError("Limit orders require 'price' parameter")
        else:
            # Market order requires at least one of shares or usdc_amount
            if shares is None and usdc_amount is None:
                raise ValueError("Market orders require either 'shares' or 'usdc_amount'")
        
        payload = {
            "token_id": token_id,
            "side": "SELL",
            "order_type": order_type,
        }
        
        if shares is not None:
            payload["shares"] = shares
        if price is not None:
            payload["price"] = price
        if usdc_amount is not None:
            payload["usdc_amount"] = usdc_amount
            
        return self._request("POST", "/api/v1/trading/orders", payload)
    
    def cancel_orders(self, order_id: str = None) -> Dict:
        """
        Cancel orders.
        
        Args:
            order_id: Specific order ID to cancel. If None, cancels ALL open orders.
        
        Returns:
            When order_id provided: Dict with message and refund_amount
            When order_id=None: Dict with message, cancelled count, total, total_refund, errors
        
        Examples:
            # Cancel a specific order
            client.cancel_orders(order_id="abc123")
            
            # Cancel all open orders
            result = client.cancel_orders()
            print(f"Cancelled {result['cancelled']} orders, refunded ${result['total_refund']}")
        """
        if order_id:
            return self._request("DELETE", f"/api/v1/trading/orders/{order_id}")
        else:
            return self._request("DELETE", "/api/v1/trading/orders")
    
    def sync_orders(self, order_id: str = None) -> Dict:
        """
        Sync order status from Polymarket.
        
        Use this to update order status if it seems stale.
        
        Args:
            order_id: Specific order ID to sync. If None, syncs ALL pending orders.
        
        Returns:
            When order_id provided: Dict with sync result
            When order_id=None: Dict with synced count, total, errors
        
        Examples:
            # Sync a specific order
            client.sync_orders(order_id="abc123")
            
            # Sync all pending orders
            result = client.sync_orders()
            print(f"Synced {result['synced']} of {result['total']} orders")
        """
        if order_id:
            return self._request("POST", f"/api/v1/trading/orders/{order_id}/sync")
        else:
            return self._request("POST", "/api/v1/trading/orders/sync-all")
    
    # ─── Market Data ────────────────────────────────────────────────────────
    
    def get_ticker(self, token_id: str) -> Dict:
        """
        Get current price ticker for a token.
        
        Args:
            token_id: The outcome token ID
        
        Returns:
            Dict with token_id, last_price, midpoint, best_bid, best_ask
        """
        return self._request("GET", f"/api/v1/markets/token/{token_id}/ticker")
