"""Load HatchDX project configuration from pyproject.toml."""

from __future__ import annotations

import platform
import shutil
import tomllib
from dataclasses import dataclass
from pathlib import Path

from rich.console import Console

from hatchdx import HdxError


class ConfigError(HdxError):
    """Raised when project configuration is missing or invalid."""


_console = Console(stderr=True)


@dataclass
class ProjectConfig:
    """Parsed [tool.hdx] configuration."""

    server_command: str
    fixtures_dir: str
    server_name: str
    container_runtime: str | None = None

    @property
    def fixtures_path(self) -> Path:
        return Path(self.fixtures_dir)


def _load_from_pyproject(root: Path) -> ProjectConfig | None:
    """Try loading config from pyproject.toml [tool.hdx] section (with [tool.mcpdx] fallback)."""
    pyproject = root / "pyproject.toml"
    if not pyproject.is_file():
        return None

    try:
        with open(pyproject, "rb") as f:
            data = tomllib.load(f)
    except tomllib.TOMLDecodeError as e:
        raise ConfigError(f"Invalid TOML in pyproject.toml: {e}")
    except (PermissionError, OSError) as e:
        raise ConfigError(f"Cannot read pyproject.toml: {e}")

    # Primary: [tool.hdx]
    hdx_config = data.get("tool", {}).get("hdx")

    # Fallback: [tool.mcpdx] with deprecation warning
    if hdx_config is None:
        hdx_config = data.get("tool", {}).get("mcpdx")
        if hdx_config is not None:
            _console.print(
                "[yellow]Warning:[/yellow] [tool.mcpdx] is deprecated. "
                "Rename to [tool.hdx] in pyproject.toml."
            )

    if hdx_config is None:
        return None

    server_command = hdx_config.get("server_command", "").strip()
    if not server_command:
        raise ConfigError(
            "Empty server_command in [tool.hdx].\n\n"
            "Provide a command to start your server:\n"
            '  [tool.hdx]\n'
            '  server_command = "python -m your_package.server"'
        )

    server_name = data.get("project", {}).get("name", "mcp-server")

    return ProjectConfig(
        server_command=server_command,
        fixtures_dir=hdx_config.get("fixtures_dir", "tests/fixtures"),
        server_name=server_name,
        container_runtime=hdx_config.get("container_runtime"),
    )


def _load_from_hdx_toml(root: Path) -> ProjectConfig | None:
    """Try loading config from standalone hdx.toml (with mcpdx.toml fallback)."""
    toml_file = root / "hdx.toml"

    # Fallback: mcpdx.toml with deprecation warning
    if not toml_file.is_file():
        legacy_file = root / "mcpdx.toml"
        if legacy_file.is_file():
            _console.print(
                "[yellow]Warning:[/yellow] mcpdx.toml is deprecated. "
                "Rename to hdx.toml."
            )
            toml_file = legacy_file
        else:
            return None

    try:
        with open(toml_file, "rb") as f:
            data = tomllib.load(f)
    except tomllib.TOMLDecodeError as e:
        raise ConfigError(f"Invalid TOML in {toml_file.name}: {e}")
    except (PermissionError, OSError) as e:
        raise ConfigError(f"Cannot read {toml_file.name}: {e}")

    server_config = data.get("server", {})
    server_command = server_config.get("command", "").strip()
    if not server_command:
        return None

    server_name = server_config.get("name", "mcp-server")
    testing_config = data.get("testing", {})
    sandbox_config = data.get("sandbox", {})

    return ProjectConfig(
        server_command=server_command,
        fixtures_dir=testing_config.get("fixtures_dir", "tests/fixtures"),
        server_name=server_name,
        container_runtime=sandbox_config.get("runtime"),
    )


def load_config(project_dir: Path | None = None) -> ProjectConfig:
    """Load project config from pyproject.toml or hdx.toml (defaults to CWD).

    Tries pyproject.toml [tool.hdx] first, then falls back to hdx.toml.
    Also reads legacy [tool.mcpdx] / mcpdx.toml with deprecation warnings.
    Raises ConfigError with actionable messages on failure.
    """
    root = Path(project_dir) if project_dir else Path.cwd()

    config = _load_from_pyproject(root) or _load_from_hdx_toml(root)
    if config is not None:
        return config

    has_pyproject = (root / "pyproject.toml").is_file()
    has_hdx_toml = (root / "hdx.toml").is_file()
    has_mcpdx_toml = (root / "mcpdx.toml").is_file()

    if not has_pyproject and not has_hdx_toml and not has_mcpdx_toml:
        raise ConfigError(
            "No pyproject.toml or hdx.toml found in current directory.\n\n"
            "Are you in an MCP server project? Run this command from your project root,\n"
            "or create a new project with: hdx server create"
        )

    if has_pyproject:
        raise ConfigError(
            "No [tool.hdx] section found in pyproject.toml.\n\n"
            "Add the following to your pyproject.toml:\n"
            '  [tool.hdx]\n'
            '  server_command = "python -m your_package.server"\n'
            '  fixtures_dir = "tests/fixtures"'
        )

    raise ConfigError(
        "hdx.toml is missing [server] section with 'command'.\n\n"
        "Add the following to your hdx.toml:\n"
        '  [server]\n'
        '  name = "my-server"\n'
        '  command = "node dist/server.js"\n\n'
        "  [testing]\n"
        '  fixtures_dir = "tests/fixtures"'
    )


def _venv_python(project_dir: Path) -> str | None:
    """Return the venv Python path if it exists, handling Windows/Unix differences."""
    if platform.system() == "Windows":
        python = project_dir / ".venv" / "Scripts" / "python.exe"
    else:
        python = project_dir / ".venv" / "bin" / "python"
    return str(python) if python.is_file() else None


def resolve_server_command(command: list[str], project_dir: Path) -> list[str]:
    """Resolve a server command to use the correct Python environment.

    Applies a resolution chain so that ``server_command = "python -m my_server"``
    works regardless of which venv hdx itself is running in:

    1. ``uv.lock`` exists → prefix with ``uv run``
    2. ``.venv/bin/python`` exists → rewrite ``python`` to the venv path
    3. ``poetry.lock`` exists → prefix with ``poetry run``
    4. Fall back to the bare command as-is
    """
    if not command:
        return command

    # Only resolve commands that start with "python" (not absolute paths or other executables)
    first = command[0]
    is_python_cmd = first in ("python", "python3")

    if not is_python_cmd:
        return command

    # 1. uv project — prefix with `uv run`
    if (project_dir / "uv.lock").is_file() and shutil.which("uv"):
        return ["uv", "run"] + command

    # 2. Local .venv — rewrite python to venv path
    venv_python = _venv_python(project_dir)
    if venv_python:
        return [venv_python] + command[1:]

    # 3. Poetry project — prefix with `poetry run`
    if (project_dir / "poetry.lock").is_file() and shutil.which("poetry"):
        return ["poetry", "run"] + command

    # 4. Fall back to bare command
    return command
