"""
Synor Mining SDK Client.
Pool connections, block templates, hashrate stats, and GPU management.
"""

import asyncio
from typing import Optional, List, Dict, Any
from urllib.parse import urlencode, quote

import httpx

from .types import (
    MiningConfig,
    PoolConfig,
    StratumConnection,
    BlockTemplate,
    TemplateTransaction,
    MinedWork,
    SubmitResult,
    ShareInfo,
    SubmitResultStatus,
    Hashrate,
    MiningStats,
    ShareStats,
    DeviceTemperature,
    EarningsSnapshot,
    TimePeriod,
    Earnings,
    EarningsBreakdown,
    MiningDevice,
    DeviceConfig,
    DeviceType,
    DeviceStatus,
    ConnectionStatus,
    WorkerInfo,
    PoolStats,
    MiningAlgorithm,
)


class MiningError(Exception):
    """Mining SDK Error."""

    def __init__(self, message: str, code: Optional[str] = None, status_code: int = 0):
        super().__init__(message)
        self.code = code
        self.status_code = status_code


class SynorMining:
    """
    Synor Mining SDK client for Python.
    Pool connections, block templates, hashrate stats, and GPU management.
    """

    def __init__(self, config: MiningConfig):
        self._config = config
        self._closed = False
        self._active_connection: Optional[StratumConnection] = None
        self._client = httpx.AsyncClient(
            base_url=config.endpoint,
            timeout=config.timeout,
            headers={
                "Authorization": f"Bearer {config.api_key}",
                "Content-Type": "application/json",
                "X-SDK-Version": "python/0.1.0",
            },
        )

    # ==================== Pool Connection ====================

    async def connect(self, pool: PoolConfig) -> StratumConnection:
        """Connect to a mining pool."""
        body = {
            "url": pool.url,
            "user": pool.user,
        }
        if pool.password:
            body["password"] = pool.password
        if pool.algorithm:
            body["algorithm"] = pool.algorithm
        if pool.difficulty:
            body["difficulty"] = pool.difficulty

        response = await self._post("/pool/connect", body)
        connection = self._parse_connection(response)
        self._active_connection = connection
        return connection

    async def disconnect(self) -> None:
        """Disconnect from the current pool."""
        if self._active_connection:
            await self._post(f"/pool/disconnect/{self._active_connection.id}", {})
            self._active_connection = None

    async def get_connection_status(self) -> Optional[StratumConnection]:
        """Get current connection status."""
        if not self._active_connection:
            return None
        try:
            response = await self._get(f"/pool/status/{self._active_connection.id}")
            return self._parse_connection(response)
        except Exception:
            return None

    async def reconnect(self) -> StratumConnection:
        """Reconnect to the pool."""
        if not self._active_connection:
            raise MiningError("No active connection to reconnect")
        response = await self._post(f"/pool/reconnect/{self._active_connection.id}", {})
        return self._parse_connection(response)

    # ==================== Mining Operations ====================

    async def get_block_template(self) -> BlockTemplate:
        """Get the current block template."""
        response = await self._get("/mining/template")
        return self._parse_block_template(response)

    async def submit_work(self, work: MinedWork) -> SubmitResult:
        """Submit mined work."""
        body = {
            "template_id": work.template_id,
            "nonce": work.nonce,
            "extra_nonce": work.extra_nonce,
            "timestamp": work.timestamp,
            "hash": work.hash,
        }
        response = await self._post("/mining/submit", body)
        return self._parse_submit_result(response)

    async def get_work(self) -> Dict[str, str]:
        """Get work from pool (stratum getwork)."""
        return await self._get("/mining/getwork")

    async def start_mining(self, algorithm: Optional[str] = None) -> Dict[str, Any]:
        """Start mining on all enabled devices."""
        body = {"algorithm": algorithm} if algorithm else {}
        return await self._post("/mining/start", body)

    async def stop_mining(self) -> Dict[str, bool]:
        """Stop mining on all devices."""
        return await self._post("/mining/stop", {})

    # ==================== Stats ====================

    async def get_hashrate(self) -> Hashrate:
        """Get current hashrate."""
        response = await self._get("/stats/hashrate")
        return self._parse_hashrate(response)

    async def get_stats(self) -> MiningStats:
        """Get mining stats."""
        response = await self._get("/stats")
        return self._parse_mining_stats(response)

    async def get_earnings(self, period: Optional[TimePeriod] = None) -> Earnings:
        """Get earnings for a time period."""
        query = f"?period={period.value}" if period else ""
        response = await self._get(f"/stats/earnings{query}")
        return self._parse_earnings(response)

    async def get_earnings_history(
        self, limit: Optional[int] = None, offset: Optional[int] = None
    ) -> List[Earnings]:
        """Get earnings history."""
        params = {}
        if limit:
            params["limit"] = str(limit)
        if offset:
            params["offset"] = str(offset)
        query = f"?{urlencode(params)}" if params else ""
        response = await self._get(f"/stats/earnings/history{query}")
        return [self._parse_earnings(e) for e in response.get("earnings", [])]

    async def get_pool_stats(self) -> PoolStats:
        """Get pool stats."""
        response = await self._get("/pool/stats")
        return PoolStats(
            url=response["url"],
            workers=response["workers"],
            hashrate=response["hashrate"],
            difficulty=response["difficulty"],
            last_block=response["last_block"],
            blocks_found_24h=response["blocks_found_24h"],
            luck=response["luck"],
        )

    # ==================== GPU Management ====================

    async def list_devices(self) -> List[MiningDevice]:
        """List all mining devices."""
        response = await self._get("/devices")
        return [self._parse_device(d) for d in response.get("devices", [])]

    async def get_device(self, device_id: str) -> MiningDevice:
        """Get device details."""
        response = await self._get(f"/devices/{quote(device_id)}")
        return self._parse_device(response)

    async def set_device_config(
        self, device_id: str, config: DeviceConfig
    ) -> MiningDevice:
        """Set device configuration."""
        body: Dict[str, Any] = {"enabled": config.enabled}
        if config.intensity is not None:
            body["intensity"] = config.intensity
        if config.power_limit is not None:
            body["power_limit"] = config.power_limit
        if config.core_clock_offset is not None:
            body["core_clock_offset"] = config.core_clock_offset
        if config.memory_clock_offset is not None:
            body["memory_clock_offset"] = config.memory_clock_offset
        if config.fan_speed is not None:
            body["fan_speed"] = config.fan_speed
        response = await self._post(f"/devices/{quote(device_id)}/config", body)
        return self._parse_device(response)

    async def enable_device(self, device_id: str) -> MiningDevice:
        """Enable a device for mining."""
        response = await self._post(f"/devices/{quote(device_id)}/enable", {})
        return self._parse_device(response)

    async def disable_device(self, device_id: str) -> MiningDevice:
        """Disable a device."""
        response = await self._post(f"/devices/{quote(device_id)}/disable", {})
        return self._parse_device(response)

    async def reset_device(self, device_id: str) -> MiningDevice:
        """Reset device to default settings."""
        response = await self._post(f"/devices/{quote(device_id)}/reset", {})
        return self._parse_device(response)

    # ==================== Workers ====================

    async def list_workers(self) -> List[WorkerInfo]:
        """List all workers."""
        response = await self._get("/workers")
        return [self._parse_worker(w) for w in response.get("workers", [])]

    async def get_worker(self, worker_id: str) -> WorkerInfo:
        """Get worker details."""
        response = await self._get(f"/workers/{quote(worker_id)}")
        return self._parse_worker(response)

    async def create_worker(self, name: str) -> WorkerInfo:
        """Create a new worker."""
        response = await self._post("/workers", {"name": name})
        return self._parse_worker(response)

    async def delete_worker(self, worker_id: str) -> None:
        """Delete a worker."""
        await self._delete(f"/workers/{quote(worker_id)}")

    # ==================== Algorithms ====================

    async def get_supported_algorithms(self) -> List[MiningAlgorithm]:
        """Get supported mining algorithms."""
        response = await self._get("/algorithms")
        return [self._parse_algorithm(a) for a in response.get("algorithms", [])]

    async def get_current_algorithm(self) -> MiningAlgorithm:
        """Get current algorithm."""
        response = await self._get("/algorithms/current")
        return self._parse_algorithm(response)

    async def switch_algorithm(self, algorithm: str) -> Dict[str, bool]:
        """Switch to a different algorithm."""
        return await self._post("/algorithms/switch", {"algorithm": algorithm})

    # ==================== Lifecycle ====================

    async def health_check(self) -> bool:
        """Health check."""
        try:
            response = await self._get("/health")
            return response.get("status") == "healthy"
        except Exception:
            return False

    @property
    def is_closed(self) -> bool:
        """Check if client is closed."""
        return self._closed

    async def close(self) -> None:
        """Close the client."""
        if self._active_connection:
            await self.disconnect()
        self._closed = True
        await self._client.aclose()

    async def __aenter__(self) -> "SynorMining":
        return self

    async def __aexit__(self, *args) -> None:
        await self.close()

    # ==================== Private Methods ====================

    async def _get(self, path: str) -> Dict[str, Any]:
        return await self._execute(lambda: self._client.get(path))

    async def _post(self, path: str, body: Dict[str, Any]) -> Dict[str, Any]:
        return await self._execute(lambda: self._client.post(path, json=body))

    async def _delete(self, path: str) -> None:
        await self._execute(lambda: self._client.delete(path))

    async def _execute(self, operation) -> Dict[str, Any]:
        if self._closed:
            raise MiningError("Client has been closed")

        last_error = None
        for attempt in range(self._config.retries):
            try:
                response = await operation()
                self._ensure_success(response)
                if response.content:
                    return response.json()
                return {}
            except Exception as e:
                last_error = e
                if self._config.debug:
                    print(f"Attempt {attempt + 1} failed: {e}")
                if attempt < self._config.retries - 1:
                    await asyncio.sleep(2**attempt)
        raise last_error

    def _ensure_success(self, response: httpx.Response) -> None:
        if response.status_code >= 400:
            message = f"HTTP {response.status_code}"
            code = None
            try:
                error = response.json()
                message = error.get("message", message)
                code = error.get("code")
            except Exception:
                pass
            raise MiningError(message, code, response.status_code)

    # ==================== Parsing Methods ====================

    def _parse_connection(self, data: Dict[str, Any]) -> StratumConnection:
        return StratumConnection(
            id=data["id"],
            pool=data["pool"],
            status=ConnectionStatus(data["status"]),
            algorithm=data["algorithm"],
            difficulty=data["difficulty"],
            connected_at=data["connected_at"],
            accepted_shares=data["accepted_shares"],
            rejected_shares=data["rejected_shares"],
            stale_shares=data["stale_shares"],
            last_share_at=data.get("last_share_at"),
        )

    def _parse_block_template(self, data: Dict[str, Any]) -> BlockTemplate:
        return BlockTemplate(
            id=data["id"],
            previous_block_hash=data["previous_block_hash"],
            merkle_root=data["merkle_root"],
            timestamp=data["timestamp"],
            bits=data["bits"],
            height=data["height"],
            coinbase_value=data["coinbase_value"],
            transactions=[
                TemplateTransaction(
                    txid=t["txid"],
                    data=t["data"],
                    fee=t["fee"],
                    weight=t["weight"],
                )
                for t in data.get("transactions", [])
            ],
            target=data["target"],
            algorithm=data["algorithm"],
            extra_nonce=data["extra_nonce"],
        )

    def _parse_submit_result(self, data: Dict[str, Any]) -> SubmitResult:
        return SubmitResult(
            status=SubmitResultStatus(data["status"]),
            share=ShareInfo(
                hash=data["share"]["hash"],
                difficulty=data["share"]["difficulty"],
                timestamp=data["share"]["timestamp"],
                accepted=data["share"]["accepted"],
            ),
            block_found=data["block_found"],
            reason=data.get("reason"),
            block_hash=data.get("block_hash"),
            reward=data.get("reward"),
        )

    def _parse_hashrate(self, data: Dict[str, Any]) -> Hashrate:
        return Hashrate(
            current=data["current"],
            average_1h=data["average_1h"],
            average_24h=data["average_24h"],
            peak=data["peak"],
            unit=data["unit"],
        )

    def _parse_mining_stats(self, data: Dict[str, Any]) -> MiningStats:
        temperature = None
        if data.get("temperature"):
            temperature = DeviceTemperature(
                current=data["temperature"]["current"],
                max=data["temperature"]["max"],
                throttling=data["temperature"]["throttling"],
            )
        return MiningStats(
            hashrate=self._parse_hashrate(data["hashrate"]),
            shares=ShareStats(
                accepted=data["shares"]["accepted"],
                rejected=data["shares"]["rejected"],
                stale=data["shares"]["stale"],
                total=data["shares"]["total"],
                accept_rate=data["shares"]["accept_rate"],
            ),
            uptime=data["uptime"],
            efficiency=data["efficiency"],
            power_consumption=data.get("power_consumption"),
            temperature=temperature,
            earnings=EarningsSnapshot(
                today=data["earnings"]["today"],
                yesterday=data["earnings"]["yesterday"],
                this_week=data["earnings"]["this_week"],
                this_month=data["earnings"]["this_month"],
                total=data["earnings"]["total"],
                currency=data["earnings"]["currency"],
            ),
        )

    def _parse_earnings(self, data: Dict[str, Any]) -> Earnings:
        return Earnings(
            period=TimePeriod(data["period"]),
            start_date=data["start_date"],
            end_date=data["end_date"],
            amount=data["amount"],
            blocks=data["blocks"],
            shares=data["shares"],
            average_hashrate=data["average_hashrate"],
            currency=data["currency"],
            breakdown=[
                EarningsBreakdown(
                    date=b["date"],
                    amount=b["amount"],
                    blocks=b["blocks"],
                    shares=b["shares"],
                    hashrate=b["hashrate"],
                )
                for b in data.get("breakdown", [])
            ],
        )

    def _parse_device(self, data: Dict[str, Any]) -> MiningDevice:
        return MiningDevice(
            id=data["id"],
            name=data["name"],
            type=DeviceType(data["type"]),
            status=DeviceStatus(data["status"]),
            hashrate=data["hashrate"],
            temperature=data["temperature"],
            fan_speed=data["fan_speed"],
            power_draw=data["power_draw"],
            memory_used=data["memory_used"],
            memory_total=data["memory_total"],
            driver=data.get("driver"),
            firmware=data.get("firmware"),
        )

    def _parse_worker(self, data: Dict[str, Any]) -> WorkerInfo:
        return WorkerInfo(
            id=data["id"],
            name=data["name"],
            status=ConnectionStatus(data["status"]),
            hashrate=self._parse_hashrate(data["hashrate"]),
            shares=ShareStats(
                accepted=data["shares"]["accepted"],
                rejected=data["shares"]["rejected"],
                stale=data["shares"]["stale"],
                total=data["shares"]["total"],
                accept_rate=data["shares"]["accept_rate"],
            ),
            devices=[self._parse_device(d) for d in data.get("devices", [])],
            last_seen=data["last_seen"],
            uptime=data["uptime"],
        )

    def _parse_algorithm(self, data: Dict[str, Any]) -> MiningAlgorithm:
        return MiningAlgorithm(
            name=data["name"],
            display_name=data["display_name"],
            hash_unit=data["hash_unit"],
            profitability=data["profitability"],
            difficulty=data["difficulty"],
            block_reward=data["block_reward"],
            block_time=data["block_time"],
        )
