"""SDK Python mínimo do Camile Creative Intelligence (CCI).

Uso:
    from camile_creative import Client
    cci = Client(api_key="...")
    result = cci.create_design({"intent": "social_announcement", "title": "Olá"})
"""

from __future__ import annotations

from pathlib import Path
from typing import Any

import httpx

DEFAULT_BASE = "https://api.camileai.com/creative/v1"


class CCIError(Exception):
    def __init__(self, status: int, code: str, message: str, details: Any = None):
        super().__init__(f"{status} {code}: {message}")
        self.status = status
        self.code = code
        self.message = message
        self.details = details


class Client:
    def __init__(self, api_key: str, base_url: str = DEFAULT_BASE, timeout: float = 90.0):
        if not api_key:
            raise ValueError("api_key obrigatória")
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout

    def _headers(self, idempotency_key: str | None = None) -> dict[str, str]:
        headers = {"x-api-key": self.api_key, "accept": "application/json"}
        if idempotency_key:
            headers["idempotency-key"] = idempotency_key
        return headers

    def _request(self, method: str, path: str, *, json: Any = None,
                 idempotency_key: str | None = None) -> Any:
        url = f"{self.base_url}{path}"
        with httpx.Client(timeout=self.timeout) as client:
            resp = client.request(method, url, headers=self._headers(idempotency_key), json=json)
        try:
            data = resp.json()
        except ValueError:
            data = {"ok": False, "code": "invalid_response", "error": resp.text[:300]}
        if resp.status_code >= 400 or not data.get("ok", True):
            raise CCIError(resp.status_code, data.get("code", "error"),
                           data.get("error", "erro desconhecido"), data.get("details"))
        return data

    def capabilities(self) -> dict[str, Any]:
        return self._request("GET", "/capabilities")

    def templates(self) -> dict[str, Any]:
        return self._request("GET", "/templates")

    def fonts(self) -> dict[str, Any]:
        return self._request("GET", "/fonts")

    def analyze(self, brief: dict[str, Any]) -> dict[str, Any]:
        return self._request("POST", "/briefs/analyze", json={"brief": brief})

    def create_design(self, brief: dict[str, Any] | None = None, *,
                      ir: dict[str, Any] | None = None, template: str | None = None,
                      render_format: str = "png", title: str | None = None,
                      idempotency_key: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"render": {"format": render_format}}
        if brief is not None:
            body["brief"] = brief
        if ir is not None:
            body["ir"] = ir
        if template:
            body["template"] = template
        if title:
            body["title"] = title
        return self._request("POST", "/designs", json=body, idempotency_key=idempotency_key)

    def get_design(self, design_id: str) -> dict[str, Any]:
        return self._request("GET", f"/designs/{design_id}")

    def list_designs(self, limit: int = 25) -> dict[str, Any]:
        return self._request("GET", f"/designs?limit={int(limit)}")

    def update_design(self, design_id: str, brief: dict[str, Any] | None = None, *,
                      ir: dict[str, Any] | None = None, template: str | None = None,
                      render_format: str = "png", title: str | None = None,
                      idempotency_key: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"render": {"format": render_format}}
        if brief is not None:
            body["brief"] = brief
        if ir is not None:
            body["ir"] = ir
        if template:
            body["template"] = template
        if title:
            body["title"] = title
        return self._request("PATCH", f"/designs/{design_id}", json=body, idempotency_key=idempotency_key)

    def revisions(self, design_id: str) -> dict[str, Any]:
        return self._request("GET", f"/designs/{design_id}/revisions")

    def get_revision(self, design_id: str, revision: int) -> dict[str, Any]:
        return self._request("GET", f"/designs/{design_id}/revisions/{int(revision)}")

    def duplicate_design(self, design_id: str, *, title: str | None = None,
                         render_format: str = "png",
                         idempotency_key: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"render": {"format": render_format}}
        if title:
            body["title"] = title
        return self._request("POST", f"/designs/{design_id}/duplicate", json=body,
                             idempotency_key=idempotency_key)

    def bundle(self, design_id: str) -> bytes:
        with httpx.Client(timeout=self.timeout) as client:
            resp = client.get(f"{self.base_url}/designs/{design_id}/bundle",
                              headers=self._headers())
        if resp.status_code >= 400:
            raise CCIError(resp.status_code, "bundle_failed", resp.text[:300])
        return resp.content

    def preview(self, design_id: str, *, width: int = 480, fmt: str = "png") -> bytes:
        with httpx.Client(timeout=self.timeout) as client:
            resp = client.get(f"{self.base_url}/designs/{design_id}/preview",
                              params={"width": int(width), "format": fmt},
                              headers=self._headers())
        if resp.status_code >= 400:
            raise CCIError(resp.status_code, "preview_failed", resp.text[:300])
        return resp.content

    def diff_revisions(self, design_id: str, from_revision: int, to_revision: int) -> dict[str, Any]:
        return self._request("GET", f"/designs/{design_id}/revisions/{int(from_revision)}"
                                    f"/diff/{int(to_revision)}")

    def restore_revision(self, design_id: str, revision: int, *,
                         render_format: str = "png",
                         idempotency_key: str | None = None) -> dict[str, Any]:
        return self._request("POST", f"/designs/{design_id}/revisions/{int(revision)}/restore"
                                     f"?format={render_format}", idempotency_key=idempotency_key)

    def render(self, design_id: str, render_format: str = "png",
               width: int | None = None, height: int | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"format": render_format}
        if width:
            body["width"] = width
        if height:
            body["height"] = height
        return self._request("POST", f"/designs/{design_id}/render", json=body)

    def campaign(self, brief: dict[str, Any], formats: list[dict[str, Any]],
                 idempotency_key: str | None = None) -> dict[str, Any]:
        return self._request("POST", "/campaigns",
                             json={"brief": brief, "formats": formats},
                             idempotency_key=idempotency_key)

    def campaign_full(self, brief: dict[str, Any], formats: list[dict[str, Any]], *,
                      video: dict[str, Any] | None = None,
                      idempotency_key: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"brief": brief, "formats": formats}
        if video is not None:
            body["video"] = video
        return self._request("POST", "/campaigns/full", json=body,
                             idempotency_key=idempotency_key)

    def animate(self, design_id: str, *, duration: float = 4,
                motion: str = "zoom_in", transition: str = "none",
                width: int | None = None, height: int | None = None,
                fps: int | None = None, audio_asset_id: str | None = None,
                audio_volume: float = 1.0, audio_loop: bool = True,
                audio_fade_in: float = 0.0, audio_fade_out: float = 1.0,
                audio_normalize: bool = False,
                callback_url: str | None = None,
                idempotency_key: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"duration": duration, "motion": motion, "transition": transition}
        if width:
            body["width"] = width
        if height:
            body["height"] = height
        if fps:
            body["fps"] = fps
        if audio_asset_id:
            body.update({"audio_asset_id": audio_asset_id, "audio_volume": audio_volume,
                         "audio_loop": audio_loop, "audio_fade_in": audio_fade_in,
                         "audio_fade_out": audio_fade_out, "normalize": audio_normalize})
        if callback_url:
            body["callback_url"] = callback_url
        return self._request("POST", f"/designs/{design_id}/animate", json=body,
                             idempotency_key=idempotency_key)

    def video(self, scenes: list[dict[str, Any]], *, width: int = 1080, height: int = 1080,
              fps: int = 24, audio: dict[str, Any] | None = None, beat_sync: bool = False,
              formats: list[str] | None = None,
              callback_url: str | None = None,
              idempotency_key: str | None = None) -> dict[str, Any]:
        body = {"scenes": scenes, "width": width, "height": height, "fps": fps}
        if audio:
            body["audio"] = audio
        if beat_sync:
            body["beat_sync"] = True
        if formats:
            body["formats"] = formats
        if callback_url:
            body["callback_url"] = callback_url
        return self._request("POST", "/videos", json=body, idempotency_key=idempotency_key)

    def job(self, job_id: str) -> dict[str, Any]:
        return self._request("GET", f"/jobs/{job_id}")

    def retry_job(self, job_id: str) -> dict[str, Any]:
        return self._request("POST", f"/jobs/{job_id}/retry")

    def cancel_job(self, job_id: str) -> dict[str, Any]:
        return self._request("POST", f"/jobs/{job_id}/cancel")

    def upload_asset(self, path: str | Path, *, audio: bool = False) -> dict[str, Any]:
        path = Path(path)
        with path.open("rb") as fh:
            files = {"file": (path.name, fh, "application/octet-stream")}
            endpoint = "/assets/audio" if audio else "/assets"
            with httpx.Client(timeout=self.timeout) as client:
                resp = client.post(f"{self.base_url}{endpoint}", headers=self._headers(), files=files)
        data = resp.json()
        if resp.status_code >= 400 or not data.get("ok", True):
            raise CCIError(resp.status_code, data.get("code", "error"), data.get("error", "erro"))
        return data

    def upload_audio(self, path: str | Path) -> dict[str, Any]:
        return self.upload_asset(path, audio=True)

    def transform_asset(self, asset_id: str, operations: list[dict[str, Any]], *,
                        output_format: str = "png", quality: int = 92,
                        background: str = "#FFFFFF", filename: str | None = None,
                        idempotency_key: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {
            "operations": operations, "output_format": output_format,
            "quality": int(quality), "background": background,
        }
        if filename:
            body["filename"] = filename
        return self._request("POST", f"/assets/{asset_id}/transform", json=body,
                             idempotency_key=idempotency_key)

    def list_assets(self, limit: int = 25) -> dict[str, Any]:
        return self._request("GET", f"/assets?limit={int(limit)}")

    def get_asset(self, asset_id: str) -> dict[str, Any]:
        return self._request("GET", f"/assets/{asset_id}")

    # ----- edição por nó (agent-native) -----
    def add_node(self, design_id: str, node: dict[str, Any], *, parent_id: str | None = None,
                 index: int | None = None, render_format: str = "png",
                 idempotency_key: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"node": node, "render": {"format": render_format}}
        if parent_id:
            body["parent_id"] = parent_id
        if index is not None:
            body["index"] = int(index)
        return self._request("POST", f"/designs/{design_id}/nodes", json=body,
                             idempotency_key=idempotency_key)

    def update_node(self, design_id: str, node_id: str, patch: dict[str, Any], *,
                    render_format: str = "png",
                    idempotency_key: str | None = None) -> dict[str, Any]:
        return self._request("PATCH", f"/designs/{design_id}/nodes/{node_id}",
                             json={"patch": patch, "render": {"format": render_format}},
                             idempotency_key=idempotency_key)

    def remove_node(self, design_id: str, node_id: str, *, render_format: str = "png",
                    idempotency_key: str | None = None) -> dict[str, Any]:
        return self._request("DELETE",
                             f"/designs/{design_id}/nodes/{node_id}?format={render_format}",
                             idempotency_key=idempotency_key)

    def materialize_layout(self, ir: dict[str, Any]) -> dict[str, Any]:
        return self._request("POST", "/layouts/materialize", json={"ir": ir})

    # ----- billing -----
    def estimate(self, operation: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
        return self._request("POST", "/billing/estimate",
                             json={"operation": operation, "params": params or {}})

    def price_book(self) -> dict[str, Any]:
        return self._request("GET", "/billing/price-book")

    def billing_plan(self) -> dict[str, Any]:
        return self._request("GET", "/billing/plan")

    def ledger(self, limit: int = 50) -> dict[str, Any]:
        return self._request("GET", f"/billing/ledger?limit={int(limit)}")

    def reservations(self, limit: int = 50) -> dict[str, Any]:
        return self._request("GET", f"/billing/reservations?limit={int(limit)}")

    # ----- providers e planejamento -----
    def providers(self) -> dict[str, Any]:
        return self._request("GET", "/providers")

    def generate_image(self, prompt: str, *, width: int = 1024, height: int = 1024,
                       seed: int | None = None, provider: str | None = None,
                       idempotency_key: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"prompt": prompt, "width": int(width), "height": int(height)}
        if seed is not None:
            body["seed"] = int(seed)
        if provider:
            body["provider"] = provider
        return self._request("POST", "/generations", json=body, idempotency_key=idempotency_key)

    def plan_brief(self, goal: str, *, tone: str = "", language: str = "pt-BR",
                   target_format: dict[str, Any] | None = None, provider: str | None = None,
                   idempotency_key: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"goal": goal, "tone": tone, "language": language}
        if target_format:
            body["format"] = target_format
        if provider:
            body["provider"] = provider
        return self._request("POST", "/planning/brief", json=body, idempotency_key=idempotency_key)

    def metrics(self) -> dict[str, Any]:
        return self._request("GET", "/metrics")

    def wait_job(self, job_id: str, *, timeout: float = 300.0,
                 interval: float = 1.0) -> dict[str, Any]:
        import time as _time
        deadline = _time.monotonic() + max(1.0, float(timeout))
        while _time.monotonic() < deadline:
            job = self.job(job_id)
            if job.get("status") in {"succeeded", "failed", "cancelled"}:
                return job
            _time.sleep(max(0.1, interval))
        raise TimeoutError(f"job {job_id} não terminou em {timeout}s")

    def signed_url(self, artifact_id: str, ttl: int = 3600) -> dict[str, Any]:
        return self._request("GET", f"/artifacts/{artifact_id}/signed-url?ttl={int(ttl)}")

    def download(self, artifact_id: str, output: str | Path) -> Path:
        output = Path(output)
        with httpx.Client(timeout=self.timeout) as client:
            resp = client.get(f"{self.base_url}/artifacts/{artifact_id}/content", headers=self._headers())
        if resp.status_code >= 400:
            raise CCIError(resp.status_code, "download_failed", resp.text[:300])
        output.write_bytes(resp.content)
        return output

    def usage(self, days: int = 30) -> dict[str, Any]:
        return self._request("GET", f"/usage/summary?days={int(days)}")

    # ----- procedural, brand, workflow e gateway -----
    def procedural_styles(self) -> dict[str, Any]:
        return self._request("GET", "/procedural/styles")

    def procedural_render(self, style: str = "mesh", *, seed: int = 7,
                          width: int = 1080, height: int = 1080,
                          density: float = 1.0, phase: float = 0.0,
                          title: str | None = None, eyebrow: str | None = None,
                          tone: str | None = None, palette: str | None = None,
                          render_format: str = "png",
                          idempotency_key: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"style": style, "seed": int(seed),
                                "width": int(width), "height": int(height),
                                "density": float(density), "phase": float(phase),
                                "format": render_format}
        for key, value in (("title", title), ("eyebrow", eyebrow),
                           ("tone", tone), ("palette", palette)):
            if value:
                body[key] = value
        return self._request("POST", "/procedural/render", json=body,
                             idempotency_key=idempotency_key)

    def procedural_video(self, styles: list[str], *, duration: float = 3,
                         fps: int = 24, width: int = 1080, height: int = 1080,
                         seed: int = 7, density: float = 1.0,
                         title: str | None = None, eyebrow: str | None = None,
                         tone: str | None = None, palette: str | None = None,
                         transition: str = "fade", formats: list[str] | None = None,
                         idempotency_key: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"styles": styles, "duration": float(duration),
                                "fps": int(fps), "width": int(width),
                                "height": int(height), "seed": int(seed),
                                "density": float(density), "transition": transition}
        for key, value in (("title", title), ("eyebrow", eyebrow),
                           ("tone", tone), ("palette", palette), ("formats", formats)):
            if value:
                body[key] = value
        return self._request("POST", "/videos/procedural", json=body,
                             idempotency_key=idempotency_key)

    def brand_kit(self) -> dict[str, Any]:
        return self._request("GET", "/brand-kit")

    def set_brand_kit(self, *, name: str = "", colors: list[str] | None = None,
                      fonts: dict[str, Any] | None = None,
                      logo_asset_id: str | None = None,
                      logo_position: str = "bottom_right",
                      logo_size: int | None = None, logo_margin: int | None = None,
                      tone: str = "", palette: str = "",
                      metadata: dict[str, Any] | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"name": name, "colors": colors or [],
                                "logo_position": logo_position,
                                "tone": tone, "palette": palette}
        if fonts:
            body["fonts"] = fonts
        if logo_asset_id:
            body["logo_asset_id"] = logo_asset_id
        if logo_size is not None:
            body["logo_size"] = int(logo_size)
        if logo_margin is not None:
            body["logo_margin"] = int(logo_margin)
        if metadata:
            body["metadata"] = metadata
        return self._request("PUT", "/brand-kit", json=body)

    def delete_brand_kit(self) -> dict[str, Any]:
        return self._request("DELETE", "/brand-kit")

    def export_html(self, design_id: str) -> str:
        with httpx.Client(timeout=self.timeout) as client:
            resp = client.get(f"{self.base_url}/designs/{design_id}/export/html",
                              headers=self._headers())
        if resp.status_code >= 400:
            raise CCIError(resp.status_code, "export_failed", resp.text[:300])
        return resp.text

    def brief_from_image(self, asset_id: str, *, goal: str = "",
                         language: str = "pt-BR", tone: str = "",
                         provider: str | None = None,
                         idempotency_key: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"asset_id": asset_id, "goal": goal,
                                "language": language, "tone": tone}
        if provider:
            body["provider"] = provider
        return self._request("POST", "/briefs/from-image", json=body,
                             idempotency_key=idempotency_key)

    def workflow(self, steps: list[dict[str, Any]], *,
                 outputs: list[str] | None = None,
                 idempotency_key: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"steps": steps}
        if outputs:
            body["outputs"] = outputs
        return self._request("POST", "/workflows/run", json=body,
                             idempotency_key=idempotency_key)

    def gateway_status(self) -> dict[str, Any]:
        return self._request("GET", "/billing/gateway/status")

    def checkout(self, plan_id: str, *, success_url: str | None = None,
                 cancel_url: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"plan_id": plan_id}
        if success_url:
            body["success_url"] = success_url
        if cancel_url:
            body["cancel_url"] = cancel_url
        return self._request("POST", "/billing/gateway/checkout", json=body)

    def portal(self, *, return_url: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {}
        if return_url:
            body["return_url"] = return_url
        return self._request("POST", "/billing/gateway/portal", json=body)

