"""CLI entry point for ``django-autoapp-init``.

Creates a fully bootstrapped Django project with a virtual environment
and ``django_autoapp`` pre-installed in a single command.

Usage::

    django-autoapp-init <project_name>

Resulting layout::

    ./venv/                ← isolated virtual environment
    ./<project_name>/      ← Django project root (manage.py lives here)
"""

from __future__ import annotations

import argparse
import platform
import re
import shutil
import subprocess
import sys
import textwrap
import venv
from pathlib import Path
from typing import NoReturn

__all__: list[str] = ["main"]

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

_PROJECT_NAME_RE: re.Pattern[str] = re.compile(r"^[a-zA-Z][a-zA-Z0-9_]*$")

_IS_WINDOWS: bool = platform.system() == "Windows"

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _log(icon: str, message: str) -> None:
    """Print a formatted status message to stdout.

    Args:
        icon: Emoji or symbol prefix.
        message: Human-readable status line.
    """
    print(f"  {icon}  {message}")


def _error(message: str) -> NoReturn:
    """Print an error message and exit with code 1.

    Args:
        message: Human-readable error description.
    """
    print(f"\n  ❌  Error: {message}", file=sys.stderr)
    raise SystemExit(1)


def _get_venv_python(venv_dir: Path) -> Path:
    """Return the path to the Python interpreter inside a virtual environment.

    Args:
        venv_dir: Root of the virtual environment.

    Returns:
        Absolute path to the ``python`` executable.
    """
    if _IS_WINDOWS:
        return venv_dir / "Scripts" / "python.exe"
    return venv_dir / "bin" / "python"


def _get_activate_hint(venv_dir: Path, project_name: str) -> str:
    """Return platform-appropriate activation instructions.

    Args:
        venv_dir: Root of the virtual environment.
        project_name: Name of the created Django project.

    Returns:
        Multi-line instruction string.
    """
    if _IS_WINDOWS:
        activate = f"..\\venv\\Scripts\\activate"
    else:
        activate = f"source ../venv/bin/activate"

    return textwrap.dedent(f"""\

        ✅  Project '{project_name}' created successfully!

        Next steps:
          cd {project_name}
          {activate}
          python manage.py runserver
    """)


def _run(
    cmd: list[str | Path],
    *,
    cwd: Path | None = None,
    label: str = "",
) -> None:
    """Execute a subprocess command with clean error handling.

    Args:
        cmd: Command and arguments to execute.
        cwd: Working directory for the subprocess.
        label: Human-readable label for error messages.

    Raises:
        SystemExit: If the subprocess returns a non-zero exit code.
    """
    try:
        subprocess.run(
            [str(c) for c in cmd],
            cwd=str(cwd) if cwd else None,
            check=True,
            capture_output=True,
            text=True,
        )
    except subprocess.CalledProcessError as exc:
        detail = exc.stderr.strip() or exc.stdout.strip() or str(exc)
        _error(f"{label} failed:\n{detail}")


def _cleanup(*paths: Path) -> None:
    """Remove directories created during a failed initialisation.

    Args:
        *paths: Directory paths to remove if they exist.
    """
    for path in paths:
        if path.exists():
            shutil.rmtree(path, ignore_errors=True)


# ---------------------------------------------------------------------------
# Settings patcher
# ---------------------------------------------------------------------------


def _patch_installed_apps(settings_path: Path) -> None:
    """Insert ``"django_autoapp"`` into ``INSTALLED_APPS`` if not present.

    Uses AST-safe line-level parsing: finds the ``INSTALLED_APPS`` list,
    then inserts the entry before the closing bracket.

    Args:
        settings_path: Absolute path to the Django ``settings.py`` file.

    Raises:
        SystemExit: If ``INSTALLED_APPS`` cannot be located in the file.
    """
    content: str = settings_path.read_text(encoding="utf-8")

    # Already patched — idempotent
    if "django_autoapp" in content:
        _log("⏭️", "django_autoapp already in INSTALLED_APPS — skipped")
        return

    # Find the closing bracket of INSTALLED_APPS
    marker = "INSTALLED_APPS"
    marker_idx = content.find(marker)
    if marker_idx == -1:
        _error("Could not locate INSTALLED_APPS in settings.py")

    # Find the closing ']' of the INSTALLED_APPS list
    bracket_idx = content.find("]", marker_idx)
    if bracket_idx == -1:
        _error("Could not find closing bracket of INSTALLED_APPS in settings.py")

    # Determine indentation from the last entry before the bracket
    last_newline = content.rfind("\n", marker_idx, bracket_idx)
    if last_newline != -1:
        existing_line = content[last_newline + 1 : bracket_idx]
        indent = ""
        for ch in existing_line:
            if ch in (" ", "\t"):
                indent += ch
            else:
                break
    else:
        indent = "    "

    insertion = f'{indent}"django_autoapp",\n'
    patched = content[:bracket_idx] + insertion + content[bracket_idx:]
    settings_path.write_text(patched, encoding="utf-8")
    _log("✏️", "Added django_autoapp to INSTALLED_APPS")


# ---------------------------------------------------------------------------
# Core initialiser
# ---------------------------------------------------------------------------


def _init_project(project_name: str) -> None:
    """Create a Django project with an isolated virtual environment.

    Steps:
        1. Validate the project name.
        2. Check that target directories do not already exist.
        3. Create virtual environment at ``./venv/``.
        4. Install Django and django-autoapp inside the venv.
        5. Scaffold the Django project via ``django-admin startproject``.
        6. Patch ``INSTALLED_APPS`` in the generated ``settings.py``.
        7. Print next-step instructions.

    Args:
        project_name: The Django project name (must be a valid Python identifier).
    """
    cwd: Path = Path.cwd()
    venv_dir: Path = cwd / "venv"
    project_dir: Path = cwd / project_name

    # --- Validate name ------------------------------------------------
    if not _PROJECT_NAME_RE.match(project_name):
        _error(
            f"Invalid project name '{project_name}'. "
            "Must start with a letter and contain only letters, digits, and underscores."
        )

    # --- Guard against existing directories ---------------------------
    if venv_dir.exists():
        _error(f"Directory already exists: {venv_dir}")

    if project_dir.exists():
        _error(f"Directory already exists: {project_dir}")

    # --- Step 1: Create virtual environment ---------------------------
    _log("📦", "Creating virtual environment …")
    try:
        venv.create(str(venv_dir), with_pip=True)
    except Exception as exc:
        _cleanup(venv_dir)
        _error(f"Failed to create virtual environment: {exc}")

    venv_python: Path = _get_venv_python(venv_dir)

    if not venv_python.exists():
        _cleanup(venv_dir)
        _error(f"Python interpreter not found at {venv_python}")

    # --- Step 2: Install Django ---------------------------------------
    _log("📥", "Installing Django …")
    try:
        _run(
            [venv_python, "-m", "pip", "install", "--upgrade", "pip"],
            label="pip upgrade",
        )
        _run(
            [venv_python, "-m", "pip", "install", "Django>=4.2"],
            label="Django install",
        )
    except SystemExit:
        _cleanup(venv_dir)
        raise

    # --- Step 3: Install django-autoapp -------------------------------
    _log("📥", "Installing django-autoapp …")
    try:
        _run(
            [venv_python, "-m", "pip", "install", "django-autoapp"],
            label="django-autoapp install",
        )
    except SystemExit:
        _cleanup(venv_dir)
        raise

    # --- Step 4: Create Django project --------------------------------
    _log("🏗️", f"Creating Django project '{project_name}' …")
    django_admin: Path
    if _IS_WINDOWS:
        django_admin = venv_dir / "Scripts" / "django-admin.exe"
    else:
        django_admin = venv_dir / "bin" / "django-admin"

    try:
        _run(
            [django_admin, "startproject", project_name, str(project_dir)],
            label="django-admin startproject",
        )
    except SystemExit:
        _cleanup(venv_dir, project_dir)
        raise

    # --- Step 5: Patch settings.py ------------------------------------
    _log("⚙️", "Configuring settings.py …")
    settings_path: Path = project_dir / project_name / "settings.py"

    if not settings_path.exists():
        _cleanup(venv_dir, project_dir)
        _error(f"settings.py not found at {settings_path}")

    try:
        _patch_installed_apps(settings_path)
    except SystemExit:
        _cleanup(venv_dir, project_dir)
        raise

    # --- Done ---------------------------------------------------------
    print(_get_activate_hint(venv_dir, project_name))


# ---------------------------------------------------------------------------
# Argument parser
# ---------------------------------------------------------------------------


def _build_parser() -> argparse.ArgumentParser:
    """Build the CLI argument parser.

    Returns:
        Configured ``argparse.ArgumentParser`` instance.
    """
    parser = argparse.ArgumentParser(
        prog="django-autoapp-init",
        description=(
            "Bootstrap a new Django project with a virtual environment "
            "and django-autoapp pre-installed."
        ),
    )
    parser.add_argument(
        "project_name",
        type=str,
        help="Name of the Django project to create (valid Python identifier)",
    )
    return parser


def main() -> None:
    """Entry point for the ``django-autoapp-init`` console script."""
    parser = _build_parser()
    args = parser.parse_args()
    _init_project(args.project_name)


if __name__ == "__main__":
    main()
