"""Python API for cascade business logic."""

from __future__ import annotations

import json
import mimetypes
import re
import time
import uuid
from pathlib import Path
from typing import Any

from cascade_fm.core.commit import CommitResult


class CascadeAPI:
    """Python API for cascade business logic.

    Called directly by the desktop UI to manage pipeline state and operations.
    """

    def __init__(self, workflow_dir: Path | None = None) -> None:
        """Initialize the API with empty pipeline state."""
        self._pipeline_id = str(uuid.uuid4())
        self._panes: list[dict[str, Any]] = []
        self._workflow_dir = workflow_dir or Path.home() / ".cascade" / "workflows"

    @staticmethod
    def commit_result_to_dict(result: CommitResult) -> dict[str, int]:
        """Serialize commit result to a reusable dictionary payload.

        Args:
            result: Commit result from commit engine.

        Returns:
            Dictionary payload that can be shared across UI/CLI layers.
        """
        return {
            "committed": result.committed,
            "skipped": result.skipped,
            "failed": result.failed,
        }

    @staticmethod
    def format_commit_summary(result: CommitResult) -> dict[str, Any]:
        """Build a user-facing commit summary payload.

        Args:
            result: Commit result from commit engine.

        Returns:
            Summary dictionary containing status level and message.
        """
        payload: dict[str, Any] = dict(CascadeAPI.commit_result_to_dict(result))

        if result.failed == 0 and result.skipped == 0:
            payload["level"] = "success"
            payload["message"] = f"✓ Committed {result.committed} item(s)"
            return payload

        if result.failed == 0:
            payload["level"] = "warning"
            payload["message"] = f"⚠ Committed {result.committed}, skipped {result.skipped}"
            return payload

        payload["level"] = "warning"
        payload["message"] = (
            f"⚠ Committed {result.committed}, skipped {result.skipped}, failed {result.failed}"
        )
        return payload

    def get_pipeline(self) -> dict[str, Any]:
        """Get current pipeline state.

        Returns:
            Pipeline state with id and list of panes.
        """
        return {
            "id": self._pipeline_id,
            "panes": self._panes,
        }

    def add_pane(self, operation: str, params: dict[str, Any]) -> dict[str, Any]:
        """Add a new pane to the pipeline.

        Args:
            operation: Operation name.
            params: Operation parameters.

        Returns:
            Updated pipeline state.
        """
        pane_id = f"pane-{len(self._panes) + 1}"
        self._panes.append(
            {
                "id": pane_id,
                "type": "operation",
                "status": "configured",
                "operation": operation,
                "params": params,
            }
        )
        return self.get_pipeline()

    def update_pane(self, pane_id: str, params: dict[str, Any]) -> dict[str, Any]:
        """Update pane configuration.

        Args:
            pane_id: ID of the pane to update.
            params: New parameters.

        Returns:
            Updated pipeline state.
        """
        for pane in self._panes:
            if pane["id"] == pane_id:
                pane["params"] = params
                pane["status"] = "configured"
                break
        return self.get_pipeline()

    def execute_pane(self, pane_id: str) -> dict[str, Any]:
        """Execute a pane.

        Args:
            pane_id: ID of the pane to execute.

        Returns:
            Updated pipeline state.
        """
        for pane in self._panes:
            if pane["id"] == pane_id:
                pane["status"] = "executing"
                # TODO: Actually execute the operation
                pane["status"] = "done"
                break
        return self.get_pipeline()

    def remove_pane(self, pane_id: str) -> dict[str, Any]:
        """Remove pane and all panes to its right.

        Args:
            pane_id: ID of the pane to remove.

        Returns:
            Updated pipeline state.
        """
        idx = next(
            (i for i, pane in enumerate(self._panes) if pane["id"] == pane_id),
            None,
        )
        if idx is not None:
            self._panes = self._panes[:idx]
        return self.get_pipeline()

    def list_directory(self, path: str) -> list[dict[str, Any]]:
        """List directory contents.

        Args:
            path: Directory path to list.

        Returns:
            List of file entries with name, path, size, type, and mime_type.
        """
        directory = Path(path).expanduser().resolve()

        if not directory.exists():
            return []

        if not directory.is_dir():
            return []

        entries: list[dict[str, Any]] = []

        try:
            for item in sorted(directory.iterdir(), key=lambda p: (not p.is_dir(), p.name.lower())):
                # Get mime type
                mime_type = "inode/directory" if item.is_dir() else None
                if not mime_type:
                    mime_type, _ = mimetypes.guess_type(str(item))
                    mime_type = mime_type or "application/octet-stream"

                # Get size (0 for directories)
                size = 0
                if item.is_file():
                    try:
                        size = item.stat().st_size
                    except OSError:
                        pass

                entries.append(
                    {
                        "name": item.name,
                        "path": str(item),
                        "size": size,
                        "type": "directory" if item.is_dir() else "file",
                        "mime_type": mime_type,
                    }
                )
        except PermissionError:
            pass

        return entries

    def get_operations(self) -> list[dict[str, Any]]:
        """List available operations.

        Returns:
            List of operation definitions.
        """
        # TODO: Implement operation registry
        return []

    def get_operation_schema(self, name: str) -> dict[str, Any]:
        """Get parameter schema for an operation.

        Args:
            name: Operation name.

        Returns:
            Operation schema with parameter definitions.
        """
        # TODO: Implement schema retrieval
        return {"name": name, "params": {}}

    def save_workflow(self, name: str, pipeline: dict[str, Any]) -> bool:
        """Save pipeline as workflow.

        Args:
            name: Workflow name.
            pipeline: Pipeline state to save.

        Returns:
            True if successful.
        """
        if not name.strip():
            return False

        try:
            workflow_path = self._workflow_path(name)
            workflow_path.parent.mkdir(parents=True, exist_ok=True)

            payload = {
                "name": name.strip(),
                "version": 1,
                "pipeline": pipeline,
            }
            workflow_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
            return True
        except (OSError, TypeError, ValueError):
            return False

    def load_workflow(self, name: str) -> dict[str, Any]:
        """Load saved workflow.

        Args:
            name: Workflow name to load.

        Returns:
            Pipeline state.
        """
        try:
            workflow_path = self._workflow_path(name)
            payload = json.loads(workflow_path.read_text(encoding="utf-8"))
        except (OSError, ValueError, json.JSONDecodeError):
            return self.get_pipeline()

        pipeline_data = payload.get("pipeline") if isinstance(payload, dict) else None
        if not isinstance(pipeline_data, dict):
            return self.get_pipeline()

        loaded_pipeline = dict(pipeline_data)

        loaded_id = loaded_pipeline.get("id")
        loaded_panes = loaded_pipeline.get("panes")

        if isinstance(loaded_id, str) and loaded_id:
            self._pipeline_id = loaded_id
        else:
            loaded_pipeline["id"] = self._pipeline_id

        if isinstance(loaded_panes, list):
            self._panes = loaded_panes
        else:
            loaded_pipeline["panes"] = []

        return loaded_pipeline

    def delete_workflow(self, name: str) -> bool:
        """Delete a saved workflow file.

        Args:
            name: Workflow name to delete.

        Returns:
            True when workflow file is removed or did not exist.
        """
        if not name.strip():
            return False

        try:
            workflow_path = self._workflow_path(name)
        except ValueError:
            return False

        try:
            workflow_path.unlink(missing_ok=True)
            return True
        except OSError:
            return False

    def get_workflow_age_seconds(self, name: str) -> float | None:
        """Return age of a saved workflow file in seconds.

        Args:
            name: Workflow name.

        Returns:
            Age in seconds, or None when workflow does not exist.
        """
        try:
            workflow_path = self._workflow_path(name)
        except ValueError:
            return None

        try:
            mtime = workflow_path.stat().st_mtime
        except OSError:
            return None

        return max(0.0, time.time() - mtime)

    def _workflow_path(self, name: str) -> Path:
        """Build a safe workflow file path for a workflow name."""
        normalized_name = re.sub(r"[^A-Za-z0-9._-]+", "_", name.strip()).strip("._")
        if not normalized_name:
            raise ValueError("Invalid workflow name")

        return self._workflow_dir / f"{normalized_name}.json"
