"""Pydantic models for Clore.ai API responses.

Models are aligned with real API response structures (verified via curl).
All models use `extra="allow"` so they won't break when the API adds new fields.
"""

from typing import Any, Dict, List, Optional

from pydantic import BaseModel, ConfigDict, Field


# ──────────────────────────────────────────────
# Shared / nested sub-models
# ──────────────────────────────────────────────


class NetworkSpecs(BaseModel):
    """Network speed & location info inside specs."""

    model_config = ConfigDict(extra="allow")

    cc: Optional[str] = None  # Country code (ISO 2-letter)
    down: Optional[float] = None  # Download Mbps
    up: Optional[float] = None  # Upload Mbps
    test_history: Optional[List[str]] = None


class ServerSpecs(BaseModel):
    """Hardware specs nested under ``specs`` key."""

    model_config = ConfigDict(extra="allow")

    mb: Optional[str] = None  # Motherboard
    cpu: Optional[str] = None  # CPU model string
    cpus: Optional[str] = None  # "cores/threads" e.g. "16/32"
    ram: Optional[float] = None  # RAM in GB
    disk: Optional[str] = None  # Disk description + size
    disk_speed: Optional[float] = None  # Disk speed MB/s
    gpu: Optional[str] = None  # e.g. "1x NVIDIA GeForce RTX 4090"
    gpuram: Optional[float] = None  # GPU VRAM in GB
    net: Optional[NetworkSpecs] = None
    backend_version: Optional[int] = None
    pcie_rev: Optional[int] = None
    pcie_width: Optional[int] = None
    xfs: Optional[int] = None
    pl: Optional[List[int]] = None  # Power limits
    stock_pl: Optional[List[int]] = None
    stock_oc: Optional[Any] = None  # Can be string or list of dicts


class ServerRating(BaseModel):
    """Server rating sub-object."""

    model_config = ConfigDict(extra="allow")

    avg: Optional[float] = None
    cnt: Optional[int] = None


# ──────────────────────────────────────────────
# Marketplace price sub-models
# ──────────────────────────────────────────────


class PriceTier(BaseModel):
    """A single payment-method price tier (on_demand / spot)."""

    model_config = ConfigDict(extra="allow")

    bitcoin: Optional[float] = None
    CLORE_Blockchain: Optional[float] = Field(None, alias="CLORE-Blockchain")
    USD_Blockchain: Optional[float] = Field(None, alias="USD-Blockchain")


class PriceUSD(BaseModel):
    """USD-equivalent prices."""

    model_config = ConfigDict(extra="allow", populate_by_name=True)

    on_demand_btc: Optional[float] = None
    on_demand_clore: Optional[float] = None
    on_demand_usd: Optional[float] = None
    spot: Optional[float] = None


class OriginalUSDEntry(BaseModel):
    """Per-coin original USD prices."""

    model_config = ConfigDict(extra="allow")

    on_demand: Optional[float] = None
    spot: Optional[float] = None


class ServerPrice(BaseModel):
    """Marketplace server price wrapper."""

    model_config = ConfigDict(extra="allow", populate_by_name=True)

    on_demand: Optional[PriceTier] = None
    spot: Optional[PriceTier] = None
    usd: Optional[PriceUSD] = None
    original_usd: Optional[Dict[str, OriginalUSDEntry]] = None


# ──────────────────────────────────────────────
# Wallet
# ──────────────────────────────────────────────


class Wallet(BaseModel):
    """Wallet balance from ``GET /v1/wallets``."""

    model_config = ConfigDict(extra="allow")

    name: str  # e.g. "bitcoin", "CLORE-Blockchain", "USD-Blockchain"
    deposit: Optional[str] = None  # Deposit address
    balance: Optional[float] = None  # Can be null for legacy wallets
    withdrawal_fee: Optional[float] = None


# ──────────────────────────────────────────────
# Marketplace Server
# ──────────────────────────────────────────────


class MarketplaceServer(BaseModel):
    """A server listing from ``GET /v1/marketplace``."""

    model_config = ConfigDict(extra="allow", populate_by_name=True)

    id: int
    owner: Optional[int] = None
    autoprice: Optional[str] = None
    mrl: Optional[int] = None  # Minimum rental length (hours or seconds depending on context)
    price: Optional[ServerPrice] = None
    rented: Optional[bool] = None
    specs: Optional[ServerSpecs] = None
    reliability: Optional[float] = None
    allowed_coins: Optional[List[str]] = None
    rating: Optional[ServerRating] = None
    gpu_array: Optional[List[Any]] = None  # Usually List[str], but can have nested lists
    gigaspot: Optional[Any] = None  # Can be bool or dict
    cuda_version: Optional[str] = None

    # ── Convenience properties ──────────────────

    def _flat_gpu_array(self) -> List[str]:
        """Flatten gpu_array (handles nested lists like [["GTX 745"]])."""
        if not self.gpu_array:
            return []
        result: List[str] = []
        for item in self.gpu_array:
            if isinstance(item, str):
                result.append(item.strip())
            elif isinstance(item, list):
                for sub in item:
                    if isinstance(sub, str):
                        result.append(sub.strip())
        return result

    @property
    def gpu_model(self) -> Optional[str]:
        """Primary GPU description from specs (e.g. '1x NVIDIA GeForce RTX 4090')."""
        if self.specs and self.specs.gpu:
            return self.specs.gpu
        flat = self._flat_gpu_array()
        return flat[0] if flat else None

    @property
    def gpu_count(self) -> int:
        """Number of GPUs (derived from gpu_array length)."""
        flat = self._flat_gpu_array()
        return len(flat) if flat else 0

    @property
    def ram_gb(self) -> Optional[float]:
        """RAM in GB from specs."""
        return self.specs.ram if self.specs else None

    @property
    def price_usd(self) -> Optional[float]:
        """On-demand price in USD (convenience shortcut)."""
        if self.price and self.price.usd:
            return self.price.usd.on_demand_usd
        return None

    @property
    def spot_price_usd(self) -> Optional[float]:
        """Spot price in USD (from usd sub-object)."""
        if self.price and self.price.usd:
            return self.price.usd.spot
        return None

    @property
    def available(self) -> bool:
        """Whether server is available (not rented)."""
        return not self.rented if self.rented is not None else True

    @property
    def location(self) -> Optional[str]:
        """Country code from network specs."""
        if self.specs and self.specs.net:
            return self.specs.net.cc
        return None


# Backward-compatible alias
Server = MarketplaceServer


# ──────────────────────────────────────────────
# My Servers (hosting side)
# ──────────────────────────────────────────────


class CurrencyPricing(BaseModel):
    """Per-currency on_demand/spot USD prices."""

    model_config = ConfigDict(extra="allow")

    on_demand: Optional[float] = None
    spot: Optional[float] = None


class MyServer(BaseModel):
    """A server you own/host from ``GET /v1/my_servers``."""

    model_config = ConfigDict(extra="allow", populate_by_name=True)

    id: int
    name: Optional[str] = None
    connected: Optional[bool] = None
    visibility: Optional[str] = None  # "public" / "private"
    pricing: Optional[Dict[str, float]] = None  # {currency: rate}
    min_spot_pricing: Optional[Dict[str, float]] = None
    online: Optional[bool] = None
    is_vpn_enabled: Optional[bool] = None
    gpu_array: Optional[List[Any]] = None
    max_fair_price_usd: Optional[float] = None
    allowed_currencies: Optional[List[str]] = None
    mass_price_changeable: Optional[bool] = None
    gpu_count: Optional[int] = None
    usd_pricing: Optional[Dict[str, CurrencyPricing]] = None
    autoprice: Optional[Dict[str, str]] = None
    oc_gpus: Optional[str] = None  # JSON string of OC ranges
    specs: Optional[ServerSpecs] = None
    lock_balance: Optional[float] = None
    lock_balance_initial_update: Optional[str] = None
    working_properly: Optional[bool] = None
    monitoring: Optional[int] = None

    # ── Convenience properties ──────────────────

    def _flat_gpu_array(self) -> List[str]:
        """Flatten gpu_array."""
        if not self.gpu_array:
            return []
        result: List[str] = []
        for item in self.gpu_array:
            if isinstance(item, str):
                result.append(item.strip())
            elif isinstance(item, list):
                for sub in item:
                    if isinstance(sub, str):
                        result.append(sub.strip())
        return result

    @property
    def gpu_model(self) -> Optional[str]:
        """Primary GPU description."""
        if self.specs and self.specs.gpu:
            return self.specs.gpu
        flat = self._flat_gpu_array()
        return flat[0] if flat else None

    @property
    def ram_gb(self) -> Optional[float]:
        """RAM in GB from specs."""
        return self.specs.ram if self.specs else None

    @property
    def status(self) -> str:
        """Human-readable status string."""
        if not self.connected:
            return "Disconnected"
        if not self.online:
            return "Offline"
        if self.working_properly is False:
            return "Not Working"
        return "Online"


# ──────────────────────────────────────────────
# Server Config (detailed, from GET /v1/server_config)
# ──────────────────────────────────────────────


class BackgroundJob(BaseModel):
    """Background job configuration on a server."""

    model_config = ConfigDict(extra="allow")

    times_updated: Optional[int] = None
    image: Optional[str] = None
    command: Optional[str] = None
    env: Optional[Dict[str, str]] = None


class ServerConfig(BaseModel):
    """Detailed server configuration from ``GET /v1/server_config``."""

    model_config = ConfigDict(extra="allow", populate_by_name=True)

    name: Optional[str] = None
    connected: Optional[bool] = None
    visibility: Optional[str] = None
    pricing: Optional[Dict[str, float]] = None
    usd_pricing: Optional[Dict[str, CurrencyPricing]] = None
    spot_pricing: Optional[Dict[str, float]] = None
    mrl: Optional[int] = None
    online: Optional[bool] = None
    initialized: Optional[bool] = None
    id: Optional[int] = None
    rental_status: Optional[int] = None
    allowed_coins: Optional[List[str]] = None
    is_vpn_enabled: Optional[bool] = None
    is_partner_enabled: Optional[bool] = None
    remaining_time: Optional[float] = None
    gpu_array: Optional[List[Any]] = None
    monitoring: Optional[int] = None
    specs: Optional[ServerSpecs] = None
    background_job: Optional[BackgroundJob] = None
    mfp_usd: Optional[float] = None  # Max fair price USD
    kernel: Optional[str] = None
    allow_oc: Optional[bool] = None
    oc_gpus: Optional[str] = None
    xorg_valid: Optional[bool] = None
    onboard_type: Optional[int] = None
    clock_lock_ranges: Optional[Dict[str, Any]] = None

    # ── Convenience properties ──────────────────

    @property
    def gpu_model(self) -> Optional[str]:
        if self.specs and self.specs.gpu:
            return self.specs.gpu
        if self.gpu_array:
            for item in self.gpu_array:
                if isinstance(item, str):
                    return item.strip()
                if isinstance(item, list):
                    for sub in item:
                        if isinstance(sub, str):
                            return sub.strip()
        return None

    @property
    def on_demand_price(self) -> Optional[float]:
        """First available on-demand USD price."""
        if self.usd_pricing:
            for _currency, prices in self.usd_pricing.items():
                if prices and prices.on_demand is not None:
                    return prices.on_demand
        return None

    @property
    def spot_price(self) -> Optional[float]:
        """First available spot USD price."""
        if self.usd_pricing:
            for _currency, prices in self.usd_pricing.items():
                if prices and prices.spot is not None:
                    return prices.spot
        return None


# ──────────────────────────────────────────────
# Orders
# ──────────────────────────────────────────────


class Order(BaseModel):
    """An order from ``GET /v1/my_orders`` or ``POST /v1/create_order``."""

    model_config = ConfigDict(extra="allow", populate_by_name=True)

    id: int
    server_id: Optional[int] = Field(None, alias="renting_server")
    type: Optional[str] = None  # "on-demand" or "spot"
    status: Optional[str] = None
    image: Optional[str] = None
    currency: Optional[str] = None
    price: Optional[float] = None
    created_at: Optional[str] = None
    pub_cluster: Optional[str] = None  # Public IP
    tcp_ports: Optional[Dict[str, int]] = None  # Port mappings
    http_port: Optional[int] = None
    env: Optional[Dict[str, str]] = None
    ssh_password: Optional[str] = None
    ssh_key: Optional[str] = None
    command: Optional[str] = None
    jupyter_token: Optional[str] = None


# ──────────────────────────────────────────────
# Spot Marketplace
# ──────────────────────────────────────────────


class SpotServerInfo(BaseModel):
    """Server info inside spot marketplace response."""

    model_config = ConfigDict(extra="allow")

    min_pricing: Optional[Dict[str, float]] = None
    mrl: Optional[int] = None
    visibility: Optional[str] = None
    online: Optional[bool] = None


class SpotOffer(BaseModel):
    """A single offer in the spot marketplace."""

    model_config = ConfigDict(extra="allow")

    order_id: Optional[int] = None
    price: Optional[float] = None
    server_id: Optional[int] = None


class SpotMarket(BaseModel):
    """Spot marketplace data from ``GET /v1/spot_marketplace``."""

    model_config = ConfigDict(extra="allow")

    currency_rates_in_usd: Optional[Dict[str, float]] = None
    offers: Optional[List[SpotOffer]] = None
    server: Optional[SpotServerInfo] = None


# ──────────────────────────────────────────────
# Generic API response
# ──────────────────────────────────────────────


class APIResponse(BaseModel):
    """Generic API response wrapper."""

    model_config = ConfigDict(extra="allow")

    code: int
    message: Optional[str] = None
    data: Optional[Any] = None
