"""
Syriatel Cash API Client

Supports both async and sync operations.
"""

import asyncio
import aiohttp
import requests
from typing import Optional, List, Union
from .exceptions import raise_error, NetworkError
from .models import BalanceResponse, HistoryResponse, Transaction, NumberWithCode


class SyriatelCashClient:
    """
    Async client for Syriatel Cash API
    
    Example:
        ```python
        import asyncio
        from syr_cash_api import SyriatelCashClient
        
        async def main():
            client = SyriatelCashClient(api_token="your_token")
            balance = await client.get_balance("0991234567")
            print(f"Balance: {balance.balance}")
        
        asyncio.run(main())
        ```
    """
    
    def __init__(
        self,
        api_token: str,
        base_url: str = "https://api.melchersman.com/syr-cash/v1",
        timeout: int = 30,
        session: Optional[aiohttp.ClientSession] = None,
    ):
        """
        Initialize the async client
        
        Args:
            api_token: Your API token
            base_url: Base URL for the API (default: production URL)
            timeout: Request timeout in seconds (default: 30)
            session: Optional aiohttp ClientSession for connection pooling
        """
        self.api_token = api_token
        self.base_url = base_url.rstrip("/")
        self.timeout = aiohttp.ClientTimeout(total=timeout)
        self._session = session
        self._own_session = session is None
    
    async def _get_session(self) -> aiohttp.ClientSession:
        """Get or create aiohttp session"""
        if self._session is None or self._session.closed:
            self._session = aiohttp.ClientSession(timeout=self.timeout)
        return self._session
    
    async def close(self):
        """Close the client session"""
        if self._session and not self._session.closed and self._own_session:
            await self._session.close()
    
    async def __aenter__(self):
        """Async context manager entry"""
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """Async context manager exit"""
        await self.close()
    
    def _normalize_number(self, number: str) -> str:
        """Normalize phone number to 0XXXXXXXXX format"""
        number = number.replace(" ", "").replace("-", "")
        if number.startswith("+963"):
            number = "0" + number[4:]
        elif number.startswith("963"):
            number = "0" + number[3:]
        return number
    
    async def _request(
        self,
        method: str,
        endpoint: str,
        params: Optional[dict] = None,
    ) -> dict:
        """
        Make an async HTTP request
        
        Args:
            method: HTTP method (GET, POST, etc.)
            endpoint: API endpoint (without base URL)
            params: Query parameters
            
        Returns:
            Response data dictionary
            
        Raises:
            NetworkError: If network request fails
            Various API errors based on response code
        """
        url = f"{self.base_url}/{endpoint.lstrip('/')}"
        headers = {"api-token": self.api_token}
        
        try:
            session = await self._get_session()
            async with session.request(
                method, url, headers=headers, params=params
            ) as response:
                try:
                    data = await response.json()
                except Exception:
                    raise NetworkError(
                        f"Invalid JSON response: {response.status}",
                        code="NETWORK_ERROR",
                    )
                
                if not data.get("success", False):
                    code = data.get("code", "UNKNOWN_ERROR")
                    raise_error(code, data=data.get("data"))
                
                return data.get("data", {})
        except aiohttp.ClientError as e:
            raise NetworkError(
                f"Network request failed: {str(e)}",
                code="NETWORK_ERROR",
            )
        except Exception as e:
            # Re-raise if it's already a SyriatelCashError
            if isinstance(e, (NetworkError,)):
                raise
            # Otherwise wrap it
            raise NetworkError(
                f"Unexpected error: {str(e)}",
                code="NETWORK_ERROR",
            )
    
    async def get_balance(self, number: str) -> BalanceResponse:
        """
        Get Syriatel Cash balance for a phone number
        
        Args:
            number: Phone number in format 0XXXXXXXXX (will be normalized)
            
        Returns:
            BalanceResponse with balance amount
            
        Raises:
            InvalidTokenError: If API token is invalid
            SubscriptionExpiredError: If subscription is expired
            FetchFailedError: If fetch from Syriatel failed
            NotAuthorizedError: If account is not authorized
            ServerMaintenanceError: If servers are under maintenance
            NetworkError: If network request fails
        """
        normalized = self._normalize_number(number)
        data = await self._request("GET", "/Balance", params={"number": normalized})
        return BalanceResponse.from_dict(data)
    
    async def get_incoming_history(
        self,
        number: Optional[str] = None,
        code: Optional[str] = None,
        page: int = 1,
    ) -> Union[HistoryResponse, List[NumberWithCode]]:
        """
        Get incoming transaction history
        
        Args:
            number: Phone number (optional, will be normalized if provided)
            code: Secret code (optional, alternative to number)
            page: Page number for pagination (default: 1)
            
        Returns:
            HistoryResponse if number/code provided, or List[NumberWithCode] if neither provided
            
        Raises:
            InvalidTokenError: If API token is invalid
            SubscriptionExpiredError: If subscription is expired
            FetchFailedError: If fetch from Syriatel failed
            NotAuthorizedError: If account is not authorized
            NetworkError: If network request fails
        """
        params = {"page": page}
        if number:
            params["number"] = self._normalize_number(number)
        elif code:
            params["code"] = code
        
        data = await self._request("GET", "/IncomingHistory", params=params)
        
        # If no number/code provided, return list of numbers
        if "numbers" in data:
            return [NumberWithCode.from_dict(n) for n in data["numbers"]]
        
        return HistoryResponse.from_dict(data)
    
    async def get_outgoing_history(
        self,
        number: Optional[str] = None,
        code: Optional[str] = None,
        page: int = 1,
    ) -> Union[HistoryResponse, List[NumberWithCode]]:
        """
        Get outgoing transaction history
        
        Args:
            number: Phone number (optional, will be normalized if provided)
            code: Secret code (optional, alternative to number)
            page: Page number for pagination (default: 1)
            
        Returns:
            HistoryResponse if number/code provided, or List[NumberWithCode] if neither provided
            
        Raises:
            InvalidTokenError: If API token is invalid
            SubscriptionExpiredError: If subscription is expired
            FetchFailedError: If fetch from Syriatel failed
            NotAuthorizedError: If account is not authorized
            NetworkError: If network request fails
        """
        params = {"page": page}
        if number:
            params["number"] = self._normalize_number(number)
        elif code:
            params["code"] = code
        
        data = await self._request("GET", "/OutgoingHistory", params=params)
        
        # If no number/code provided, return list of numbers
        if "numbers" in data:
            return [NumberWithCode.from_dict(n) for n in data["numbers"]]
        
        return HistoryResponse.from_dict(data)


class SyriatelCashClientSync:
    """
    Synchronous client for Syriatel Cash API
    
    This is a wrapper around the async client that provides sync methods.
    
    Example:
        ```python
        from syr_cash_api import SyriatelCashClientSync
        
        client = SyriatelCashClientSync(api_token="your_token")
        balance = client.get_balance("0991234567")
        print(f"Balance: {balance.balance}")
        ```
    """
    
    def __init__(
        self,
        api_token: str,
        base_url: str = "https://api.melchersman.com/syr-cash/v1",
        timeout: int = 30,
    ):
        """
        Initialize the sync client
        
        Args:
            api_token: Your API token
            base_url: Base URL for the API (default: production URL)
            timeout: Request timeout in seconds (default: 30)
        """
        self._async_client = SyriatelCashClient(
            api_token=api_token,
            base_url=base_url,
            timeout=timeout,
        )
        self._loop = None
    
    def _run_async(self, coro):
        """Run async coroutine in sync context"""
        try:
            # Check if we're already in an async context
            asyncio.get_running_loop()
            raise RuntimeError(
                "Cannot use sync client inside async context. Use SyriatelCashClient instead."
            )
        except RuntimeError as e:
            if "Cannot use sync client" in str(e):
                raise
            # No running loop, safe to create one
            pass
        
        # Create or get event loop
        try:
            loop = asyncio.get_event_loop()
            if loop.is_closed():
                loop = asyncio.new_event_loop()
                asyncio.set_event_loop(loop)
        except RuntimeError:
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
        
        return loop.run_until_complete(coro)
    
    def close(self):
        """Close the client"""
        if self._async_client:
            self._run_async(self._async_client.close())
    
    def __enter__(self):
        """Context manager entry"""
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit"""
        self.close()
    
    def get_balance(self, number: str) -> BalanceResponse:
        """
        Get Syriatel Cash balance for a phone number (sync)
        
        Args:
            number: Phone number in format 0XXXXXXXXX (will be normalized)
            
        Returns:
            BalanceResponse with balance amount
        """
        return self._run_async(self._async_client.get_balance(number))
    
    def get_incoming_history(
        self,
        number: Optional[str] = None,
        code: Optional[str] = None,
        page: int = 1,
    ) -> Union[HistoryResponse, List[NumberWithCode]]:
        """
        Get incoming transaction history (sync)
        
        Args:
            number: Phone number (optional, will be normalized if provided)
            code: Secret code (optional, alternative to number)
            page: Page number for pagination (default: 1)
            
        Returns:
            HistoryResponse if number/code provided, or List[NumberWithCode] if neither provided
        """
        return self._run_async(
            self._async_client.get_incoming_history(number, code, page)
        )
    
    def get_outgoing_history(
        self,
        number: Optional[str] = None,
        code: Optional[str] = None,
        page: int = 1,
    ) -> Union[HistoryResponse, List[NumberWithCode]]:
        """
        Get outgoing transaction history (sync)
        
        Args:
            number: Phone number (optional, will be normalized if provided)
            code: Secret code (optional, alternative to number)
            page: Page number for pagination (default: 1)
            
        Returns:
            HistoryResponse if number/code provided, or List[NumberWithCode] if neither provided
        """
        return self._run_async(
            self._async_client.get_outgoing_history(number, code, page)
        )

