"""
LuxurAI SDK — client.py
Main client class. Everything starts here.
"""

from __future__ import annotations
from typing import Optional, List

from .http       import HttpClient
from .models     import GenerationJob, GeneratedImage, WalletBalance, JobStatus, Resolution, APIKey
from .exceptions import LuxurAIError, AuthenticationError


# ─────────────────────────────────────────────
# Sub-clients
# ─────────────────────────────────────────────

class JobsClient:
    """client.jobs — poll and list generation jobs."""

    def __init__(self, http: HttpClient):
        self._http = http

    def get(self, job_id: str) -> GenerationJob:
        """Get current status of a job."""
        data = self._http.get(f"/api/ux/job/{job_id}")
        return _parse_job(data)

    def list(self, limit: int = 20) -> List[GenerationJob]:
        """List recent generation jobs."""
        data = self._http.get(f"/api/ux/history?limit={limit}")
        jobs = data if isinstance(data, list) else data.get("jobs", [])
        return [_parse_job(j) for j in jobs]


class WalletClient:
    """client.wallet — check LC balance."""

    def __init__(self, http: HttpClient):
        self._http = http

    def balance(self) -> WalletBalance:
        """Get current LC balance and recent ledger."""
        data   = self._http.get("/api/ux/wallet")
        lc     = float(data.get("balance_lc", 0))
        ledger = data.get("ledger", [])
        return WalletBalance(lc=lc, inr=round(lc * 0.25, 2), ledger=ledger)


class KeysClient:
    """client.keys — manage API keys."""

    def __init__(self, http: HttpClient):
        self._http = http

    def list(self) -> List[APIKey]:
        """List all API keys for your account."""
        data = self._http.get("/api/ux/apikey/list")
        keys = data if isinstance(data, list) else data.get("keys", [])
        return [_parse_key(k) for k in keys]

    def create(self, label: str = None) -> str:
        """
        Create a new API key.
        Returns the full key string — save it, it won't be shown again!
        """
        payload = {}
        if label:
            payload["label"] = label
        data = self._http.post("/api/ux/apikey/create", payload)
        return data.get("api_key") or data.get("key") or data.get("full_key", "")

    def revoke(self, key_id: str) -> bool:
        """Revoke/delete an API key by ID."""
        self._http.delete(f"/api/ux/apikey/{key_id}")
        return True

    def rotate(self, key_id: str) -> str:
        """Rotate an existing key — old key is immediately invalidated."""
        data = self._http.post(f"/api/ux/apikey/rotate?key_id={key_id}")
        return data.get("api_key") or data.get("key", "")


# ─────────────────────────────────────────────
# Main Client
# ─────────────────────────────────────────────

class LuxurAI:
    """
    LuxurAI Python SDK — Official client.

    Usage:
        from luxurai import LuxurAI

        client = LuxurAI(api_key="lxr_v1_xxxx")

        # Generate and wait
        image = client.generate("a red dragon over mountains")
        image.save("dragon.png")

        # Generate async (non-blocking)
        job = client.generate("cyberpunk city at night", wait=False)
        print(f"Queued at position #{job.queue_position}")
        image = job.wait()

        # Check balance
        balance = client.wallet.balance()
        print(f"{balance.lc} LC  ≈  ₹{balance.inr:.2f}")

        # List jobs
        for job in client.jobs.list():
            print(job.id, job.status)

    Args:
        api_key:  Your LuxurAI API key (starts with lxr_v1_)
                  Get one at https://luxurai.in → Dashboard → API Keys
        base_url: API base URL (default: https://www.luxurai.in)
        timeout:  Request timeout in seconds (default: 30)
    """

    BASE_URL = "https://www.luxurai.in"

    def __init__(
        self,
        api_key:  str,
        base_url: str = None,
        timeout:  int = 30,
    ):
        if not api_key:
            raise AuthenticationError(
                "api_key is required. Get yours at luxurai.in → Dashboard → API Keys"
            )
        if not api_key.startswith("lxr_v1_"):
            raise AuthenticationError(
                f"Invalid API key format '{api_key[:12]}...' — LuxurAI keys start with 'lxr_v1_'"
            )

        self._http = HttpClient(
            api_key  = api_key,
            base_url = base_url or self.BASE_URL,
            timeout  = timeout,
        )

        # Sub-clients
        self.jobs   = JobsClient(self._http)
        self.wallet = WalletClient(self._http)
        self.keys   = KeysClient(self._http)
        self._models_cache = None

    # ── Core: generate ────────────────────────────────────────────────

    def generate(
        self,
        prompt:       str,
        *,
        model:        str  = "brainai-v1",
        resolution:   str  = "1024x1024",
        fast:         bool = False,
        no_watermark: bool = False,
        wait:         bool = True,
        timeout:      int  = 300,
        verbose:      bool = True,
    ) -> "GeneratedImage | GenerationJob":
        """
        Generate an image from a text prompt.

        Args:
            prompt:       Text description of the image to generate
            model:        Model to use. Currently: "brainai-v1" (default)
            resolution:   Output resolution. Examples:
                            "512x512"   — SD (fast, cheap)
                            "1024x1024" — HD (default)
                            "1152x1152" — 2K (brainAI natural size)
                            "1920x1080" — FHD widescreen
                          Any WxH string works — brainAI is free-size.
            fast:         Use fast mode — lower quality, cheaper (default False)
            no_watermark: Remove watermark — costs extra LC (default False)
            wait:         If True (default), block until done and return GeneratedImage.
                          If False, return GenerationJob immediately.
            timeout:      Max seconds to wait when wait=True (default 300)
            verbose:      Print progress when wait=True (default True)

        Returns:
            GeneratedImage  if wait=True  → call .save(), .bytes(), .to_pil()
            GenerationJob   if wait=False → call .wait() or poll .status

        Raises:
            AuthenticationError:     Invalid API key
            InsufficientBalanceError: Not enough LC
            ModelTrainingError:      brainAI not deployed yet
            JobFailedError:          Generation failed
            JobTimeoutError:         Timed out waiting

        Examples:
            # Simple — blocks until done
            image = client.generate("a sunset over mountains")
            image.save("sunset.png")

            # Choose model + resolution
            image = client.generate(
                "divine krishna playing flute",
                model      = "brainai-v1",
                resolution = "1152x1152",
            )

            # Non-blocking
            job = client.generate("anime girl", wait=False)
            print(f"ETA: {job.eta_seconds}s")
            image = job.wait()
        """
        # Validate model
        supported = {"brainai-v1"}
        if model not in supported:
            raise LuxurAIError(
                f"Unknown model '{model}'. Supported: {sorted(supported)}"
            )

        # Submit job
        data = self._http.post("/api/ux/generate", {
            "prompt":       prompt,
            "resolution":   resolution,
            "fast":         fast,
            "no_watermark": no_watermark,
            "api_key":      self._http.api_key,
        })

        job = GenerationJob(
            id             = data["job_id"],
            prompt         = prompt,
            status         = JobStatus(data.get("status", "queued")),
            cost_lc        = float(data.get("cost_lc", 0)),
            resolution     = resolution,
            queue_position = data.get("queue_position"),
            eta_seconds    = data.get("eta_seconds"),
            _client        = self,
        )

        if verbose and not wait:
            pos = f" at queue position #{job.queue_position}" if job.queue_position is not None else ""
            print(f"Job submitted{pos} — {job.cost_lc} LC deducted")
            print(f"  job.id = '{job.id}'")
            print(f"  call job.wait() to get the image")

        if wait:
            return job.wait(timeout=timeout, verbose=verbose)

        return job

    # ── Models ────────────────────────────────────────────────────────

    def models(self) -> dict:
        """
        Returns available models and resolutions.
        Tries live server first; falls back to hardcoded defaults.
        Result is cached — second call returns same object instantly.
        """
        if hasattr(self, '_models_cache') and self._models_cache is not None:
            return self._models_cache
        result = {
            "models": ["brainai-v1"],
            "default": "brainai-v1",
            "resolutions": [
                "100x100", "200x200", "500x500",
                "512x512", "512x1024", "576x1024",
                "1024x512", "1024x576", "1024x1024",
                "1080x1920", "1152x1152",
                "1920x1080", "2048x2048",
            ],
            "default_resolution": "1024x1024",
            "coming_soon": True,
            "message": "",
        }
        try:
            s = self._http.get("/api/ux/generation-status")
            result["coming_soon"] = s.get("coming_soon", True)
            result["message"]     = s.get("message", "")
        except Exception:
            pass
        self._models_cache = result
        return result

    def _resolve_model(self, model: str) -> str:
        """
        Resolve model name — 'auto' picks the default.
        Raises LuxurAIError if model is unknown.
        """
        info = self.models()
        if model == "auto":
            return info["default"]
        if model not in info["models"]:
            raise LuxurAIError(
                f"Unknown model '{model}'. Available: {info['models']} "
                f"(or use model='auto' to pick default)"
            )
        return model

    def _resolve_resolution(self, resolution: str) -> str:
        """
        Resolve resolution — 'auto' picks the default.
        Any explicit WxH string is passed through as-is (brainAI is free-size).
        """
        if resolution == "auto":
            return self.models()["default_resolution"]
        return resolution

    # ── Convenience ───────────────────────────────────────────────────

    def status(self) -> dict:
        """Check if generation is available (brainAI deployed or still training)."""
        return self._http.get("/api/ux/generation-status")

    def __repr__(self) -> str:
        key_preview = self._http.api_key[:16] + "..."
        return f"LuxurAI(api_key='{key_preview}', base_url='{self._http.base_url}')"


# ─────────────────────────────────────────────
# Parsers
# ─────────────────────────────────────────────

def _parse_job(data: dict) -> GenerationJob:
    return GenerationJob(
        id             = data.get("job_id") or data.get("id", ""),
        prompt         = data.get("prompt", ""),
        status         = JobStatus(data.get("status", "pending")),
        cost_lc        = float(data.get("cost_lc", 0)),
        resolution     = data.get("resolution", "1024x1024"),
        queue_position = data.get("queue_position"),
        eta_seconds    = data.get("eta_seconds"),
        image_url      = data.get("image_url"),
    )


def _parse_key(data: dict) -> APIKey:
    return APIKey(
        id         = data.get("id", ""),
        prefix     = data.get("key_prefix") or data.get("prefix", ""),
        created_at = data.get("created_at", ""),
        is_active  = data.get("is_active", True),
        label      = data.get("label"),
    )
