"""Capsules resource — sync and async implementations."""
from __future__ import annotations
from typing import Any, BinaryIO, Dict, List, Optional, Tuple, Union

from nanorix._constants import DEFAULT_EXECUTE_TIMEOUT, DEFAULT_UPLOAD_TIMEOUT
from nanorix._exceptions import ExecutionError
from nanorix._http import AsyncHttpTransport, HttpTransport
from nanorix._models import (
    Capsule,
    CapsuleList,
    CdpAccess,
    ExecutionResult,
    FileInfo,
)


class LiveCapsule:
    """
    A running capsule with workspace methods.
    Returned by client.capsules.start().
    """

    def __init__(self, capsule: Capsule, http: HttpTransport) -> None:
        self._capsule = capsule
        self._http = http

    @property
    def id(self) -> str:
        return self._capsule.id

    @property
    def status(self) -> str:
        return self._capsule.status

    def upload(
        self,
        filename: str,
        data: Union[bytes, BinaryIO],
    ) -> FileInfo:
        """Upload a file to the capsule's input directory."""
        content = data if isinstance(data, bytes) else data.read()
        result = self._http.request(
            "POST",
            f"/v1/capsules/{self.id}/upload",
            data=content,
            headers={"Content-Type": "application/octet-stream"},
            params={"filename": filename},
            timeout=DEFAULT_UPLOAD_TIMEOUT,
        )
        return FileInfo.model_validate(result)

    def execute(
        self,
        command: Optional[str] = None,
        *,
        timeout: Optional[int] = None,
    ) -> ExecutionResult:
        """
        Execute a command in the capsule.
        If no command provided, uses the template entrypoint.
        """
        body: Dict[str, Any] = {}
        if command is not None:
            body["command"] = command
        if timeout is not None:
            body["timeout"] = timeout

        result = self._http.request(
            "POST",
            f"/v1/capsules/{self.id}/exec",
            json=body if body else None,
            timeout=DEFAULT_EXECUTE_TIMEOUT,
        )
        exec_result = ExecutionResult.model_validate(result)

        if exec_result.exit_code != 0:
            raise ExecutionError(
                f"Command exited with code {exec_result.exit_code}",
                exit_code=exec_result.exit_code,
                stdout=exec_result.stdout,
                stderr=exec_result.stderr,
            )

        return exec_result

    def download(self, filename: str) -> bytes:
        """Download an output file from the capsule."""
        return self._http.request(
            "GET",
            f"/v1/capsules/{self.id}/output/{filename}",
            raw_response=True,
        )

    def list_outputs(self) -> List[FileInfo]:
        """List all output files in the capsule."""
        result = self._http.request("GET", f"/v1/capsules/{self.id}/output")
        files_data = result.get("files", result) if isinstance(result, dict) else result
        return [FileInfo.model_validate(f) for f in files_data]

    def destroy(self) -> CdpAccess:
        """
        Destroy the capsule and return the CDP.
        After this call, the capsule is gone. Only the proof remains.
        """
        result = self._http.request("DELETE", f"/v1/capsules/{self.id}")
        return CdpAccess(
            full_data=result.get("cdp", result),
            verification_data=result.get("verification_cdp", {}),
            capsule_id=self.id,
        )

    def refresh(self) -> "LiveCapsule":
        """Refresh capsule status from API."""
        result = self._http.request("GET", f"/v1/capsules/{self.id}")
        self._capsule = Capsule.model_validate(result)
        return self


class Capsules:
    """Synchronous capsule operations."""

    def __init__(self, http: HttpTransport) -> None:
        self._http = http

    def start(
        self,
        *,
        template: Optional[str] = None,
        max_lifetime_seconds: Optional[int] = None,
    ) -> LiveCapsule:
        """Start a new capsule. Returns a LiveCapsule with workspace methods."""
        body: Dict[str, Any] = {}
        if template is not None:
            body["template_id"] = template
        if max_lifetime_seconds is not None:
            body["max_lifetime_seconds"] = max_lifetime_seconds

        result = self._http.request("POST", "/v1/capsules", json=body)
        capsule = Capsule.model_validate(result)
        return LiveCapsule(capsule, self._http)

    def run(
        self,
        *,
        template: str,
        inputs: Optional[Dict[str, Union[bytes, BinaryIO]]] = None,
        command: Optional[str] = None,
        timeout: Optional[int] = None,
        max_lifetime_seconds: Optional[int] = None,
    ) -> Tuple[Dict[str, bytes], CdpAccess]:
        """
        One-shot processing: start -> upload -> execute -> download -> destroy.

        Returns (outputs, cdp) where:
        - outputs: Dict[str, bytes] mapping filename -> content
        - cdp: CdpAccess with .full() and .verification()
        """
        capsule = self.start(template=template, max_lifetime_seconds=max_lifetime_seconds)

        try:
            if inputs:
                for filename, data in inputs.items():
                    capsule.upload(filename, data)

            capsule.execute(command=command, timeout=timeout)

            output_list = capsule.list_outputs()
            outputs: Dict[str, bytes] = {}
            for f in output_list:
                outputs[f.filename] = capsule.download(f.filename)

            cdp = capsule.destroy()
            return outputs, cdp

        except ExecutionError as e:
            try:
                cdp = capsule.destroy()
                e.cdp = cdp.full()
            except Exception:
                pass
            raise

        except Exception:
            try:
                capsule.destroy()
            except Exception:
                pass
            raise

    def list(self, **kwargs: Any) -> CapsuleList:
        """List capsules. Supports ?status= query param."""
        result = self._http.request("GET", "/v1/capsules", params=kwargs or None)
        return CapsuleList.model_validate(result)

    def get(self, capsule_id: str) -> Capsule:
        """Get a capsule by ID."""
        result = self._http.request("GET", f"/v1/capsules/{capsule_id}")
        return Capsule.model_validate(result)


class AsyncLiveCapsule:
    """Async running capsule with workspace methods."""

    def __init__(self, capsule: Capsule, http: AsyncHttpTransport) -> None:
        self._capsule = capsule
        self._http = http

    @property
    def id(self) -> str:
        return self._capsule.id

    @property
    def status(self) -> str:
        return self._capsule.status

    async def upload(
        self,
        filename: str,
        data: Union[bytes, BinaryIO],
    ) -> FileInfo:
        """Upload a file to the capsule's input directory (async)."""
        content = data if isinstance(data, bytes) else data.read()
        result = await self._http.request(
            "POST",
            f"/v1/capsules/{self.id}/upload",
            data=content,
            headers={"Content-Type": "application/octet-stream"},
            params={"filename": filename},
            timeout=DEFAULT_UPLOAD_TIMEOUT,
        )
        return FileInfo.model_validate(result)

    async def execute(
        self,
        command: Optional[str] = None,
        *,
        timeout: Optional[int] = None,
    ) -> ExecutionResult:
        """Execute a command in the capsule (async)."""
        body: Dict[str, Any] = {}
        if command is not None:
            body["command"] = command
        if timeout is not None:
            body["timeout"] = timeout

        result = await self._http.request(
            "POST",
            f"/v1/capsules/{self.id}/exec",
            json=body if body else None,
            timeout=DEFAULT_EXECUTE_TIMEOUT,
        )
        exec_result = ExecutionResult.model_validate(result)

        if exec_result.exit_code != 0:
            raise ExecutionError(
                f"Command exited with code {exec_result.exit_code}",
                exit_code=exec_result.exit_code,
                stdout=exec_result.stdout,
                stderr=exec_result.stderr,
            )

        return exec_result

    async def download(self, filename: str) -> bytes:
        """Download an output file from the capsule (async)."""
        return await self._http.request(
            "GET",
            f"/v1/capsules/{self.id}/output/{filename}",
            raw_response=True,
        )

    async def list_outputs(self) -> List[FileInfo]:
        """List all output files in the capsule (async)."""
        result = await self._http.request("GET", f"/v1/capsules/{self.id}/output")
        files_data = result.get("files", result) if isinstance(result, dict) else result
        return [FileInfo.model_validate(f) for f in files_data]

    async def destroy(self) -> CdpAccess:
        """Destroy the capsule and return the CDP (async)."""
        result = await self._http.request("DELETE", f"/v1/capsules/{self.id}")
        return CdpAccess(
            full_data=result.get("cdp", result),
            verification_data=result.get("verification_cdp", {}),
            capsule_id=self.id,
        )

    async def refresh(self) -> "AsyncLiveCapsule":
        """Refresh capsule status from API (async)."""
        result = await self._http.request("GET", f"/v1/capsules/{self.id}")
        self._capsule = Capsule.model_validate(result)
        return self


class AsyncCapsules:
    """Asynchronous capsule operations."""

    def __init__(self, http: AsyncHttpTransport) -> None:
        self._http = http

    async def start(
        self,
        *,
        template: Optional[str] = None,
        max_lifetime_seconds: Optional[int] = None,
    ) -> AsyncLiveCapsule:
        """Start a new capsule (async). Returns an AsyncLiveCapsule."""
        body: Dict[str, Any] = {}
        if template is not None:
            body["template_id"] = template
        if max_lifetime_seconds is not None:
            body["max_lifetime_seconds"] = max_lifetime_seconds

        result = await self._http.request("POST", "/v1/capsules", json=body)
        capsule = Capsule.model_validate(result)
        return AsyncLiveCapsule(capsule, self._http)

    async def run(
        self,
        *,
        template: str,
        inputs: Optional[Dict[str, Union[bytes, BinaryIO]]] = None,
        command: Optional[str] = None,
        timeout: Optional[int] = None,
        max_lifetime_seconds: Optional[int] = None,
    ) -> Tuple[Dict[str, bytes], CdpAccess]:
        """One-shot processing (async): start -> upload -> execute -> download -> destroy."""
        capsule = await self.start(template=template, max_lifetime_seconds=max_lifetime_seconds)

        try:
            if inputs:
                for filename, data in inputs.items():
                    await capsule.upload(filename, data)

            await capsule.execute(command=command, timeout=timeout)

            output_list = await capsule.list_outputs()
            outputs: Dict[str, bytes] = {}
            for f in output_list:
                outputs[f.filename] = await capsule.download(f.filename)

            cdp = await capsule.destroy()
            return outputs, cdp

        except ExecutionError as e:
            try:
                cdp = await capsule.destroy()
                e.cdp = cdp.full()
            except Exception:
                pass
            raise

        except Exception:
            try:
                await capsule.destroy()
            except Exception:
                pass
            raise

    async def list(self, **kwargs: Any) -> CapsuleList:
        """List capsules (async)."""
        result = await self._http.request("GET", "/v1/capsules", params=kwargs or None)
        return CapsuleList.model_validate(result)

    async def get(self, capsule_id: str) -> Capsule:
        """Get a capsule by ID (async)."""
        result = await self._http.request("GET", f"/v1/capsules/{capsule_id}")
        return Capsule.model_validate(result)
