"""Project resource for the Daly Energy SDK.

Projects represent solar PV development projects linked to a location.
"""

from __future__ import annotations

from contextlib import ExitStack
from pathlib import Path
from typing import TYPE_CHECKING, BinaryIO

if TYPE_CHECKING:
    from dalysdk.client import DalyClient


class ProjectResource:
    """Interact with the ``/projects`` endpoints.

    Usage::

        projects = client.projects.list()
        project = client.projects.get(1)
        new_proj = client.projects.create({"name": "Solar Farm", "locationId": 1})
        updated = client.projects.update(1, {"status": "construction"})
        client.projects.delete(1)
    """

    def __init__(self, client: DalyClient):
        self._client = client

    def list(self) -> list[dict]:
        """List all projects in the workspace.

        Returns:
            List of project dicts.
        """
        return self._client._request("GET", "/projects")

    def get(self, project_id: int) -> dict:
        """Get a project by its workspace-scoped ID.

        Args:
            project_id: Workspace-scoped project index.

        Returns:
            Project dict.

        Raises:
            NotFoundError: If the project does not exist.
        """
        return self._client._request("GET", f"/projects/{project_id}")

    def create(self, data: dict) -> dict:
        """Create a new project.

        The request must include at least one of ``locationId`` (workspace-scoped
        index) or ``globalLocationId`` (Snowflake ID) to associate the project
        with a location.

        Args:
            data: Project data. Required: ``name`` and a location reference.
                  Optional: ``status``, ``timezoneOverride``, ``boundaryGeometry``,
                  ``projectDetails``.

        Returns:
            Created project dict.
        """
        return self._client._request("POST", "/projects", json=data)

    def update(self, project_id: int, data: dict) -> dict:
        """Update an existing project.

        Only provided fields are changed (partial update).

        Args:
            project_id: Workspace-scoped project index.
            data: Fields to update. Same fields as ``create()``.

        Returns:
            Updated project dict.

        Raises:
            NotFoundError: If the project does not exist.
        """
        return self._client._request("PUT", f"/projects/{project_id}", json=data)

    def delete(self, project_id: int) -> dict:
        """Delete a project.

        Args:
            project_id: Workspace-scoped project index.

        Returns:
            Confirmation dict (``{"deleted": True}``).

        Raises:
            NotFoundError: If the project does not exist.
        """
        return self._client._request("DELETE", f"/projects/{project_id}")

    def import_from_files(
        self,
        file_paths: list[str | Path] | str | Path | None = None,
        *,
        file_objs: list[tuple[str, BinaryIO]] | None = None,
    ) -> dict:
        """Import a PVsyst project package/files via ``POST /projects/import``.

        Supports both local file paths and open binary file objects. You may
        provide either source or both in the same call.

        Args:
            file_paths: Paths to files on disk (for example ``.prj``, ``.pan``,
                ``.ond``, ``.shd``, ``.met``, ``.vx``, ``.vc``, ``.zip``).
            file_objs: Open file objects as ``(filename, file_obj)`` tuples.

        Returns:
            Import result payload containing created assets and warnings.

        Raises:
            ValueError: If no files are provided, or if a file-object tuple has
                an empty filename.
        """
        normalized_paths = [file_paths] if isinstance(file_paths, (str, Path)) else file_paths or []

        if not normalized_paths and not file_objs:
            raise ValueError("Provide at least one project file via file_paths or file_objs")

        with ExitStack() as stack:
            files: list[tuple[str, tuple[str, BinaryIO, str]]] = []

            if normalized_paths:
                for file_path in normalized_paths:
                    path = Path(file_path)
                    file_handle = stack.enter_context(path.open("rb"))
                    files.append(("files", (path.name, file_handle, "application/octet-stream")))

            if file_objs:
                for filename, file_obj in file_objs:
                    if not filename:
                        raise ValueError("Each file object must include a non-empty filename")
                    files.append(("files", (filename, file_obj, "application/octet-stream")))

            return self._client._request("POST", "/projects/import", files=files)
