import importlib.resources
import logging
from importlib.metadata import version
from pathlib import Path
from typing import Annotated

import typer

from django_new.creators.app import (
    ApiAppCreator,
    AppCreator,
    WebAppCreator,
    WorkerAppCreator,
)
from django_new.creators.project import (
    ClassicProjectCreator,
    MinimalProjectCreator,
    TemplateProjectCreator,
)
from django_new.utils import console, is_running_under_any_uv, stderr, stdout

try:
    from django.core.management.base import CommandError
except ImportError as exc:
    # This should never happen because `Django` is a dependency of `django-new`
    raise ImportError("Couldn't import Django. Are you sure it's installed?") from exc

logger = logging.getLogger(__name__)

app = typer.Typer(help="Create a new Django project.")


def version_callback(show_version: bool) -> None:  # noqa: FBT001
    """Show the version and exit."""

    if not show_version:
        return

    version_str = ""

    try:
        version_str = version("django-new")
    except ImportError:
        logger.debug("Could not get version from importlib.metadata, so falling back to reading pyproject.toml")

        try:
            from tomllib import loads as toml_loads  # noqa: PLC0415
        except ImportError:
            try:
                from tomli import loads as toml_loads  # noqa: PLC0415
            except ImportError:
                toml_loads = None

        if toml_loads:
            try:
                resource = importlib.resources.files("django_new").parent.parent / "pyproject.toml"
                version_str = toml_loads(resource.read_text()).get("project", {}).get("version")
            except Exception as e:
                logger.error("Failed to read version from pyproject.toml", exc_info=e)

    if version_str:
        typer.echo(f"django-new v{version_str}")
    else:
        typer.echo("django-new (version unknown)")

    raise typer.Exit()


def create_project(
    name: str | None = typer.Argument(None, help="Project name"),
    folder: str = typer.Argument(
        ".",
        help="Optional project folder to create the project in. Defaults to the current directory.",
    ),
    project: bool = typer.Option(False, "--project", "-p", help="Create a project without an app."),
    minimal: bool = typer.Option(False, "--minimal", "-m", help="Create a minimal project."),
    app: bool = typer.Option(False, "--app", help="Create a default app."),
    web: bool = typer.Option(False, "--web", help="Create a website."),  # noqa: FBT001
    api: bool = typer.Option(False, "--api", help="Create an API."),  # noqa: FBT001
    worker: bool = typer.Option(False, "--worker", help="Create a worker."),
    template: str | None = typer.Option(
        None,
        "--starter-kit",
        "--starter",
        "--project-template",
        "--template",
        help="Template to use to create an application. Can be a URL or a local path.",
    ),
    version: Annotated[  # noqa: ARG001
        bool | None,
        typer.Option("--version", callback=version_callback, help="Show the version."),
    ] = None,
):
    """
    Create a new Django project.
    """

    if name is None:
        # TODO: Interactive mode to prompt user for project name and options
        version_callback(show_version=True)

    name = name.strip()

    # Check for multiple flags at once that don't make sense being used together
    if sum([project, app, api, web, worker, template is not None]) > 1:
        stderr(
            "Cannot specify more than one of --project, --app, --api, --web, --worker, --starter-kit at the same time"
        )

        raise typer.Exit(1)

    console.print("\n[green4]Preparing to create Django magic with django-new ✨[/green4]\n")

    # Handle folder arg
    (folder_path, project_already_existed) = get_folder_path(name, folder)

    # Handle name normalization
    project_name = name
    app_name = get_app_name(name)

    try:
        if not app:
            if project_already_existed:
                logger.debug("Project already exists")

                if minimal:
                    stderr("Project already exists, so cannot make a minimal project")

                    raise typer.Exit(1)
                elif project:
                    stderr("Project already exists, so cannot make a project")

                    raise typer.Exit(1)
                elif template:
                    stderr("Project already exists, so cannot use a template")

                    raise typer.Exit(1)
            elif minimal:
                with console.status("Setting up your minimal project...", spinner="dots"):
                    logger.debug("Project doesn't exist; make minimal")
                    MinimalProjectCreator(name=app_name, folder=folder_path).create()
            elif template:
                with console.status("Setting up your project with starter kit...", spinner="dots"):
                    logger.debug("Project doesn't exist; make with starter kit")
                    TemplateProjectCreator(name=project_name, folder=folder_path).create(project_template=template)
            else:
                with console.status("Setting up your project...", spinner="dots"):
                    logger.debug("Project doesn't exist; make classic")
                    ClassicProjectCreator(folder=folder_path).create(display_name=project_name)

        if not project and not minimal and not template:
            subclassed_app_name = app_name

            if not project_already_existed:
                # Set this to `None` which will use the default app name for each subclass
                subclassed_app_name = None

            with console.status("Setting up your app...", spinner="dots"):
                if api:
                    ApiAppCreator(app_name=subclassed_app_name, folder=folder_path).create()
                elif web:
                    WebAppCreator(app_name=subclassed_app_name, folder=folder_path).create()
                elif worker:
                    WorkerAppCreator(app_name=subclassed_app_name, folder=folder_path).create()
                else:
                    # Always pass in the actual name for default apps
                    AppCreator(app_name=app_name, folder=folder_path).create()
    except CommandError as e:
        cmd_error = str(e)

        stderr(cmd_error)

        raise typer.Exit(1) from e

    if project_already_existed:
        stdout("\nSuccess! 🚀\n")
    else:
        run_command = "python manage.py runserver"
        cd_command = ""

        if is_running_under_any_uv():
            run_command = "uv run python manage.py runserver"

        if str(folder_path) != ".":
            cd_command = f"Enter your project directory with [green4]cd {folder_path}[/green4].\n"

        stdout(
            f"""
The new Django project is ready to go! 🚀
{cd_command}Run [green4]{run_command}[/green4] to start the development server.
"""
        )


def get_folder_path(name: str, folder: str) -> tuple[Path, bool]:
    """Get the resolved folder path."""

    project_already_existed = False
    folder_path = Path(folder).resolve()

    if str(folder_path) != ".":
        logger.debug(f"Create target directory, {folder_path}, if it doesn't exist")

        if folder_path.exists():
            project_already_existed = folder_path.is_dir() and (folder_path / "manage.py").exists()
        else:
            logger.debug(f"Create project dir {folder_path}")
            folder_path.mkdir(parents=True, exist_ok=True)
    else:
        logger.debug("Target directory is current directory")
        folder_path = Path.cwd()

    has_files = False

    for _ in folder_path.iterdir():
        logger.debug("Target directory has files/directories")
        has_files = True
        break

    if has_files and not project_already_existed:
        if folder == ".":
            response = typer.prompt(
                "Hmm, the current directory is not empty. Should a new directory be created here?", default="yes"
            )
        else:
            response = typer.prompt(
                "Hmm, the target directory is not empty. Should a new directory be created in it?", default="yes"
            )

        folder_name = name

        if response.lower() in ["y", "yes"]:
            folder_name = typer.prompt("What should be the name of the new directory?", default=name)
        else:
            response = typer.prompt(
                f"This will create files in '{folder_path}'. Are you sure you don't want to create a new directory?",
                default="yes",
            )

            if response.lower() in ["y", "yes"]:
                return (folder_path, project_already_existed)
            else:
                folder_name = typer.prompt("Oh ok! What should be the name of the new directory?", default=name)

        folder_path = folder_path / folder_name
        folder_path.mkdir(exist_ok=True)
        project_already_existed = False

    return (folder_path, project_already_existed)


def get_app_name(name: str) -> str:
    """Get the app name, handling dashes if present."""

    if "-" in name:
        potential_app_name = name.replace("-", "_")

        user_input = typer.prompt(
            f"Uh oh, dashes are not allowed in Python modules. 😞 Would you like to use '{potential_app_name}' "
            "for the app folder instead?",
            default="yes",
        )

        if user_input.lower() not in ("y", "yes"):
            console.print("[red]Please try again with a name that doesn't contain dashes.[/red]")

            raise typer.Exit(0)

        return potential_app_name

    return name


def main():
    # This is the entry point for the CLI
    app()


# Register the command
app.command()(create_project)

if __name__ == "__main__":
    app()
