"""
Synor Economics SDK Client.
Pricing, billing, staking, and discount management.
"""

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

import httpx

from .types import (
    EconomicsConfig,
    ServiceType,
    UsageMetrics,
    Price,
    UsagePlan,
    CostEstimate,
    BillingPeriod,
    Usage,
    Invoice,
    AccountBalance,
    StakeReceipt,
    UnstakeReceipt,
    StakingRewards,
    StakeInfo,
    StakeOptions,
    Discount,
    AppliedDiscount,
    TaxAmount,
    ServiceUsageRecord,
    UsageDetail,
    InvoiceItem,
    InvoiceStatus,
    StakeStatus,
    DiscountType,
    StakeRewardDetail,
)


class EconomicsError(Exception):
    """Economics 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 SynorEconomics:
    """
    Synor Economics SDK client for Python.
    Pricing, billing, staking, and discount management.
    """

    def __init__(self, config: EconomicsConfig):
        self._config = config
        self._closed = False
        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",
            },
        )

    # ==================== Pricing Operations ====================

    async def get_price(self, service: ServiceType, usage: UsageMetrics) -> Price:
        """Get price for a service based on usage."""
        response = await self._post(
            "/pricing/calculate",
            {"service": service.value, "usage": self._usage_to_dict(usage)},
        )
        return self._parse_price(response["price"])

    async def estimate_cost(self, plan: UsagePlan) -> CostEstimate:
        """Estimate cost for a usage plan."""
        response = await self._post("/pricing/estimate", self._plan_to_dict(plan))
        return self._parse_cost_estimate(response)

    async def get_pricing_tiers(self, service: ServiceType) -> List[Price]:
        """Get pricing tiers for a service."""
        response = await self._get(f"/pricing/{service.value}/tiers")
        return [self._parse_price(p) for p in response.get("tiers", [])]

    # ==================== Usage & Billing Operations ====================

    async def get_usage(self, period: Optional[BillingPeriod] = None) -> Usage:
        """Get usage for a billing period."""
        params = f"?period={period.value}" if period else ""
        response = await self._get(f"/usage{params}")
        return self._parse_usage(response)

    async def get_usage_history(
        self, limit: Optional[int] = None, offset: Optional[int] = None
    ) -> List[Usage]:
        """Get usage 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"/usage/history{query}")
        return [self._parse_usage(u) for u in response.get("usage", [])]

    async def get_invoices(self) -> List[Invoice]:
        """Get invoices."""
        response = await self._get("/invoices")
        return [self._parse_invoice(i) for i in response.get("invoices", [])]

    async def get_invoice(self, invoice_id: str) -> Invoice:
        """Get a specific invoice."""
        response = await self._get(f"/invoices/{quote(invoice_id)}")
        return self._parse_invoice(response)

    async def pay_invoice(
        self, invoice_id: str, payment_method: Optional[str] = None
    ) -> Invoice:
        """Pay an invoice."""
        response = await self._post(
            f"/invoices/{quote(invoice_id)}/pay",
            {"payment_method": payment_method} if payment_method else {},
        )
        return self._parse_invoice(response)

    async def get_balance(self) -> AccountBalance:
        """Get account balance."""
        response = await self._get("/balance")
        return self._parse_balance(response)

    async def add_funds(self, amount: str, payment_method: str) -> AccountBalance:
        """Add funds to account."""
        response = await self._post(
            "/balance/deposit", {"amount": amount, "payment_method": payment_method}
        )
        return self._parse_balance(response)

    # ==================== Staking Operations ====================

    async def stake(
        self, amount: str, options: Optional[StakeOptions] = None
    ) -> StakeReceipt:
        """Stake tokens."""
        body: Dict[str, Any] = {"amount": amount}
        if options:
            if options.validator:
                body["validator"] = options.validator
            if options.auto_compound is not None:
                body["auto_compound"] = options.auto_compound
            if options.lock_duration:
                body["lock_duration"] = options.lock_duration
        response = await self._post("/staking/stake", body)
        return self._parse_stake_receipt(response)

    async def unstake(self, stake_id: str) -> UnstakeReceipt:
        """Unstake tokens."""
        response = await self._post(f"/staking/stakes/{quote(stake_id)}/unstake", {})
        return self._parse_unstake_receipt(response)

    async def get_staking_rewards(self) -> StakingRewards:
        """Get staking rewards."""
        response = await self._get("/staking/rewards")
        return self._parse_staking_rewards(response)

    async def claim_rewards(self, stake_id: Optional[str] = None) -> StakingRewards:
        """Claim staking rewards."""
        path = (
            f"/staking/stakes/{quote(stake_id)}/claim"
            if stake_id
            else "/staking/rewards/claim"
        )
        response = await self._post(path, {})
        return self._parse_staking_rewards(response)

    async def list_stakes(self) -> List[StakeInfo]:
        """List active stakes."""
        response = await self._get("/staking/stakes")
        return [self._parse_stake_info(s) for s in response.get("stakes", [])]

    async def get_stake(self, stake_id: str) -> StakeInfo:
        """Get stake details."""
        response = await self._get(f"/staking/stakes/{quote(stake_id)}")
        return self._parse_stake_info(response)

    async def get_staking_apy(self) -> Dict[str, Any]:
        """Get current APY for staking."""
        return await self._get("/staking/apy")

    # ==================== Discount Operations ====================

    async def apply_discount(self, code: str) -> Discount:
        """Apply a discount code."""
        response = await self._post("/discounts/apply", {"code": code})
        return self._parse_discount(response)

    async def get_available_discounts(self) -> List[Discount]:
        """Get available discounts."""
        response = await self._get("/discounts")
        return [self._parse_discount(d) for d in response.get("discounts", [])]

    async def get_active_discounts(self) -> List[Discount]:
        """Get active discounts on account."""
        response = await self._get("/discounts/active")
        return [self._parse_discount(d) for d in response.get("discounts", [])]

    async def remove_discount(self, code: str) -> None:
        """Remove a discount."""
        await self._delete(f"/discounts/{quote(code)}")

    # ==================== 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."""
        self._closed = True
        await self._client.aclose()

    async def __aenter__(self) -> "SynorEconomics":
        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 EconomicsError("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 EconomicsError(message, code, response.status_code)

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

    def _usage_to_dict(self, usage: UsageMetrics) -> Dict[str, Any]:
        result = {}
        if usage.compute_hours is not None:
            result["compute_hours"] = usage.compute_hours
        if usage.storage_bytes is not None:
            result["storage_bytes"] = usage.storage_bytes
        if usage.database_ops is not None:
            result["database_ops"] = usage.database_ops
        if usage.hosting_requests is not None:
            result["hosting_requests"] = usage.hosting_requests
        if usage.bridge_transfers is not None:
            result["bridge_transfers"] = usage.bridge_transfers
        if usage.rpc_calls is not None:
            result["rpc_calls"] = usage.rpc_calls
        return result

    def _plan_to_dict(self, plan: UsagePlan) -> Dict[str, Any]:
        result: Dict[str, Any] = {
            "services": [
                {
                    "service": s.service.value,
                    "metrics": self._usage_to_dict(s.metrics),
                    "tier": s.tier,
                }
                for s in plan.services
            ],
            "period": plan.period.value,
        }
        if plan.start_date:
            result["start_date"] = plan.start_date
        if plan.end_date:
            result["end_date"] = plan.end_date
        return result

    def _parse_price(self, data: Dict[str, Any]) -> Price:
        return Price(
            service=ServiceType(data["service"]),
            base_price=data["base_price"],
            quantity=data["quantity"],
            unit=data["unit"],
            subtotal=data["subtotal"],
            discounts=[self._parse_applied_discount(d) for d in data.get("discounts", [])],
            total=data["total"],
            currency=data["currency"],
        )

    def _parse_applied_discount(self, data: Dict[str, Any]) -> AppliedDiscount:
        return AppliedDiscount(
            code=data["code"],
            type=DiscountType(data["type"]),
            value=data["value"],
            savings=data["savings"],
            description=data.get("description"),
        )

    def _parse_cost_estimate(self, data: Dict[str, Any]) -> CostEstimate:
        return CostEstimate(
            services=[self._parse_price(p) for p in data.get("services", [])],
            subtotal=data["subtotal"],
            discounts=[self._parse_applied_discount(d) for d in data.get("discounts", [])],
            taxes=[
                TaxAmount(name=t["name"], rate=t["rate"], amount=t["amount"])
                for t in data.get("taxes", [])
            ],
            total=data["total"],
            currency=data["currency"],
            valid_until=data["valid_until"],
        )

    def _parse_usage(self, data: Dict[str, Any]) -> Usage:
        return Usage(
            period=BillingPeriod(data["period"]),
            start_date=data["start_date"],
            end_date=data["end_date"],
            services=[self._parse_service_usage(s) for s in data.get("services", [])],
            total_cost=data["total_cost"],
            currency=data["currency"],
        )

    def _parse_service_usage(self, data: Dict[str, Any]) -> ServiceUsageRecord:
        metrics_data = data.get("metrics", {})
        return ServiceUsageRecord(
            service=ServiceType(data["service"]),
            metrics=UsageMetrics(
                compute_hours=metrics_data.get("compute_hours"),
                storage_bytes=metrics_data.get("storage_bytes"),
                database_ops=metrics_data.get("database_ops"),
                hosting_requests=metrics_data.get("hosting_requests"),
                bridge_transfers=metrics_data.get("bridge_transfers"),
                rpc_calls=metrics_data.get("rpc_calls"),
            ),
            cost=data["cost"],
            details=[
                UsageDetail(
                    timestamp=d["timestamp"],
                    operation=d["operation"],
                    quantity=d["quantity"],
                    unit=d["unit"],
                    cost=d["cost"],
                )
                for d in data.get("details", [])
            ],
        )

    def _parse_invoice(self, data: Dict[str, Any]) -> Invoice:
        return Invoice(
            id=data["id"],
            status=InvoiceStatus(data["status"]),
            period=BillingPeriod(data["period"]),
            start_date=data["start_date"],
            end_date=data["end_date"],
            items=[
                InvoiceItem(
                    service=ServiceType(i["service"]),
                    description=i["description"],
                    quantity=i["quantity"],
                    unit_price=i["unit_price"],
                    total=i["total"],
                )
                for i in data.get("items", [])
            ],
            subtotal=data["subtotal"],
            discounts=[self._parse_applied_discount(d) for d in data.get("discounts", [])],
            taxes=[
                TaxAmount(name=t["name"], rate=t["rate"], amount=t["amount"])
                for t in data.get("taxes", [])
            ],
            total=data["total"],
            currency=data["currency"],
            due_date=data["due_date"],
            paid_at=data.get("paid_at"),
            payment_method=data.get("payment_method"),
        )

    def _parse_balance(self, data: Dict[str, Any]) -> AccountBalance:
        return AccountBalance(
            available=data["available"],
            pending=data["pending"],
            reserved=data["reserved"],
            total=data["total"],
            currency=data["currency"],
            last_updated=data["last_updated"],
            credit_limit=data.get("credit_limit"),
        )

    def _parse_stake_receipt(self, data: Dict[str, Any]) -> StakeReceipt:
        return StakeReceipt(
            id=data["id"],
            amount=data["amount"],
            lock_duration=data["lock_duration"],
            start_date=data["start_date"],
            end_date=data["end_date"],
            apy=data["apy"],
            estimated_rewards=data["estimated_rewards"],
            tx_hash=data["tx_hash"],
        )

    def _parse_unstake_receipt(self, data: Dict[str, Any]) -> UnstakeReceipt:
        return UnstakeReceipt(
            id=data["id"],
            stake_id=data["stake_id"],
            amount=data["amount"],
            rewards=data["rewards"],
            total=data["total"],
            unbonding_period=data["unbonding_period"],
            available_at=data["available_at"],
            tx_hash=data["tx_hash"],
        )

    def _parse_stake_info(self, data: Dict[str, Any]) -> StakeInfo:
        return StakeInfo(
            id=data["id"],
            amount=data["amount"],
            status=StakeStatus(data["status"]),
            lock_duration=data["lock_duration"],
            start_date=data["start_date"],
            end_date=data["end_date"],
            apy=data["apy"],
            earned_rewards=data["earned_rewards"],
            pending_rewards=data["pending_rewards"],
            validator=data.get("validator"),
        )

    def _parse_staking_rewards(self, data: Dict[str, Any]) -> StakingRewards:
        return StakingRewards(
            total_earned=data["total_earned"],
            pending=data["pending"],
            claimed=data["claimed"],
            stakes=[
                StakeRewardDetail(
                    stake_id=s["stake_id"],
                    earned=s["earned"],
                    pending=s["pending"],
                    apy=s["apy"],
                )
                for s in data.get("stakes", [])
            ],
            last_claim_date=data.get("last_claim_date"),
            next_claim_available=data.get("next_claim_available"),
        )

    def _parse_discount(self, data: Dict[str, Any]) -> Discount:
        return Discount(
            code=data["code"],
            type=DiscountType(data["type"]),
            value=data["value"],
            description=data["description"],
            valid_from=data["valid_from"],
            valid_until=data["valid_until"],
            applicable_services=[ServiceType(s) for s in data.get("applicable_services", [])],
            used_count=data["used_count"],
            min_purchase=data.get("min_purchase"),
            max_discount=data.get("max_discount"),
            usage_limit=data.get("usage_limit"),
        )
