from __future__ import annotations
from typing import Literal

from pathlib import Path
import platform

from ..config import BaseConfig, SettingsConfigDict, Field

# TODO: put this in a dedicated package and add it as dependency:
# from tgzr.package_manager import PackageManager
from tgzr.shell.package_manager import PackageManager


class WorkspacesConfig(BaseConfig):
    model_config = SettingsConfigDict(env_prefix="tgzr_ws_")

    default_workspace_name: str | None = Field(
        None, description="Name of the default workspace"
    )
    default_index: str | None = Field(
        None,
        description="Default package index for all workspaces. Use None for https://pypi.org/simple",
    )


class Workspaces:
    def __init__(self, parent: Path | str):
        parent = Path(parent)
        if not parent.is_dir():
            raise ValueError(
                f'The path "{parent}" is not a directory, cannot instantiate Workspaces here.'
            )
        self._home = (parent / "Workspaces").resolve()
        if not self._home.exists():
            self._home.mkdir()
        self._config_path = self._home / ".tgzr_workspaces"
        self._config = WorkspacesConfig.from_file(self._config_path)

    @property
    def home(self) -> Path:
        return self._home

    @property
    def config(self) -> WorkspacesConfig:
        return self._config

    def workspace_names(self) -> list[str]:
        """
        Return the names of the existing workspaces.
        """
        if not self._home.exists():
            return []
        names = []
        for path in self._home.iterdir():
            config_path = Workspace.config_path(self, path.name)
            if config_path.exists():
                names.append(path.name)
        return sorted(names)

    def get_workspaces(self) -> list[Workspace]:
        return [
            self.get_workspace(name, ensure_exists=False)
            for name in self.workspace_names()
        ]

    def set_default_workspace(
        self, workspace_name: str | None, ensure_exists: bool = True
    ) -> None:
        if workspace_name is None:
            self._config.default_workspace_name = None
        else:
            if ensure_exists:
                # will raise if it does not exists:
                self.get_workspace(workspace_name, ensure_exists=True)
            self._config.default_workspace_name = workspace_name
        self.save_config()

    def save_config(self) -> Path:
        """Returns the path of the saved file."""
        self._config.to_file(
            self._config_path,
            allow_overwrite=True,
            header_text="tgzr workspaces config",
        )
        return self._config_path

    def default_workspace_name(self) -> str | None:
        return self.config.default_workspace_name

    def get_default_workspace(self, ensure_exists: bool = True) -> Workspace | None:
        name = self._config.default_workspace_name
        if name is None:
            return None
        return self.get_workspace(name, ensure_exists)

    def get_workspace(self, name: str, ensure_exists: bool = True) -> Workspace:
        ws = Workspace(self, name)
        if ensure_exists and not ws.exists():
            raise FileNotFoundError
        return ws

    def resolve_index(
        self, index: str | None, relative_to: Path | None = None
    ) -> str | None:
        """
        Returns the path of an index, making it absolute in case
        of relative local path.
        If `relative_to` is not provided, `self.home` is used.

        If index is None, the default index will be returned.
        If no default index is set, None is returned.
        """
        index = index or self.config.default_index
        if index is None:
            return None
        if not "://" in index:
            local_index = Path(index)
            if not local_index.is_absolute():
                relative_to = relative_to or self.home
                index = str((relative_to / local_index).resolve())
        return index


class WorkspaceConfig(BaseConfig):
    model_config = SettingsConfigDict(env_prefix="tgzr_ws_default_")

    default_index: str | None = Field(
        None,
        description="Default package index for this workspace. Use None to fallback to Worksapces config",
    )


class Workspace:
    @classmethod
    def config_path(cls, workspaces: Workspaces, name: str) -> Path:
        return workspaces.home / name / ".tgzr_workspace"

    def __init__(self, workspaces: Workspaces, name: str) -> None:
        self._workspaces = workspaces
        self._name = name
        self._config_path = self.config_path(workspaces, name)
        self._config = WorkspaceConfig.from_file(self._config_path)

        self._tgzr_venv_group = "_"
        self._tgzr_venv_name = "tgzr"
        self._projects_venv_group = "projects"

    @property
    def config(self) -> WorkspaceConfig:
        return self._config

    @property
    def name(self) -> str:
        return self._name

    @property
    def workspaces(self) -> Workspaces:
        return self._workspaces

    @property
    def path(self) -> Path:
        return self.workspaces.home / self.name

    # @property
    # def default_index(self) -> str | None:
    #     return (
    #         self.config.default_index or self._workspaces.config.default_index or None
    #     )

    def resolve_index(self, index: str | None) -> str | None:
        """
        Returns the path of an index, making it absolute in case
        of relative local path.

        If index is None, the default index will be returned.
        If no default index in found on the Workspace nor on the Workspaces,
        None is returned.
        """
        index = index or self.config.default_index
        return self._workspaces.resolve_index(index, relative_to=self.path)

    def save_config(self) -> Path:
        """Returns the path of the saved file."""
        self._config.to_file(
            self._config_path,
            allow_overwrite=True,
            header_text="tgzr workspaces config",
        )
        return self._config_path

    def exists(self) -> bool:
        return self.path.exists()

    def ensure_exists(self):
        if not self.exists:
            self.create()

    def create(self, index: str | None = None):
        """
        Create (or update) this workspace and seed it with the `tgzr.cli` and `uv` packages.
        If `index` is None, the parent workspaces' default index is used.
        """
        self.path.mkdir(exist_ok=True)
        self.save_config()

        venvs = self.path / "venvs"
        pm = PackageManager(venvs)
        venv = pm.create_venv(
            self._tgzr_venv_name, group=self._tgzr_venv_group, exist_ok=True
        )

        index = self.resolve_index(index)
        ok = venv.install_package("tgzr.cli", index=index, update=True)
        if not ok:
            raise Exception("Could not install tgzr in workspace :( Aborting.")

        tgzr_exe = venv.get_exe("tgzr")
        tgzr_shortcut = self.path / "tgzr"
        if tgzr_shortcut.exists():
            tgzr_shortcut.unlink()
        pm.create_shortcut(tgzr_exe, tgzr_shortcut, relative=True)

    def run_cmd(
        self, cmd_name: str, cmd_args: list[str], project_name: str | None = None
    ):
        """
        Run a command in the venv for the given project.
        If project_name is not provided, the "master" venv is used ('_/tgzr')

        Returns True if the command was executed successfuly (according to process return int).
        """
        if project_name is None:
            group = self._tgzr_venv_group
            venv_name = self._tgzr_venv_name
        else:
            group = self._projects_venv_group
            venv_name = project_name

        venvs = self.path / "venvs"
        pm = PackageManager(venvs)
        venv = pm.get_venv(venv_name, group)
        return venv.run_cmd(cmd_name, cmd_args)

    def shell(
        self,
        shell_type: Literal["xonsh", "cmd", "powershell", "auto"],
        project_name: str | None = None,
    ):
        """
        Open a shell in the venv for the given project.
        If project_name is not provided, the "master" venv is used ('_/tgzr')

        Returns True if the command was executed successfuly (according to process return int).
        """
        if project_name is None:
            group = self._tgzr_venv_group
            venv_name = self._tgzr_venv_name
        else:
            group = self._projects_venv_group
            venv_name = project_name

        venvs = self.path / "venvs"
        pm = PackageManager(venvs)
        venv = pm.get_venv(venv_name, group)

        if shell_type == "auto":
            if platform.system() == "Windows":
                shell_type = "cmd"
            else:
                shell_type = "xonsh"

        if shell_type == "xonsh":
            xonsh = venv.get_exe("xonsh")
            if not xonsh.exists():
                venv.install_package("xonsh[full]")
            uv = venv.get_exe("uv")
            cmd = f"{uv} run xonsh"
        else:
            activate = str(venv.get_exe("activate")).replace("/", "\\")
            cmd = f"start cmd /k {activate}"

        return venv.execute_cmd(cmd)
