"""Project initialization helpers for qpip."""

from __future__ import annotations

import os
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Callable, Mapping, Optional, Sequence, TextIO

from .project_run import DEFAULT_QPIP_SCRIPTS, build_scripts_section, toml_string

DEFAULT_PROJECT_VERSION = "0.1.0"
DEFAULT_REQUIRES_PYTHON = ">=3.8"
DEFAULT_LICENSE = "MIT"

PROJECT_NAME_PATTERN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")


class InitUsageError(ValueError):
    """Raised when qpip init usage is invalid."""


@dataclass(frozen=True)
class InitConfig:
    """Metadata collected for a new Python project."""

    name: str
    version: str
    description: str
    requires_python: str
    author_name: str
    author_email: str
    license_text: str


PromptFunc = Callable[[str], str]


def normalize_project_name(name: str) -> str:
    """Normalize a directory name into a safe default project name."""
    candidate = re.sub(r"\s+", "-", name.strip().lower())
    candidate = re.sub(r"[^a-z0-9._-]+", "-", candidate).strip("-._")
    return candidate or "my-project"


def validate_project_name(name: str) -> None:
    """Validate a PEP 621 compatible project name."""
    if not PROJECT_NAME_PATTERN.match(name):
        raise InitUsageError("项目名称只允许字母、数字、点、下划线和短横线，且必须以字母或数字开头")


def build_init_defaults(
    cwd: Path,
    env: Optional[Mapping[str, str]] = None,
) -> InitConfig:
    """Build default answers for qpip init."""
    env_map = os.environ if env is None else env
    return InitConfig(
        name=normalize_project_name(cwd.name),
        version=DEFAULT_PROJECT_VERSION,
        description="",
        requires_python=DEFAULT_REQUIRES_PYTHON,
        author_name=(
            env_map.get("QPIP_INIT_AUTHOR")
            or env_map.get("GIT_AUTHOR_NAME")
            or env_map.get("USERNAME")
            or env_map.get("USER")
            or ""
        ),
        author_email=env_map.get("QPIP_INIT_EMAIL") or env_map.get("GIT_AUTHOR_EMAIL") or "",
        license_text=DEFAULT_LICENSE,
    )


def prompt_value(label: str, default: str, input_func: PromptFunc) -> str:
    """Prompt for a value while supporting a default."""
    suffix = f" ({default})" if default else ""
    value = input_func(f"{label}{suffix}: ").strip()
    return value or default


def prompt_init_config(
    cwd: Path,
    defaults: InitConfig,
    input_func: PromptFunc,
    output: TextIO,
) -> InitConfig:
    """Interactively collect project metadata."""
    print(f"将在 {cwd} 中初始化 Python 项目。", file=output)
    print("直接回车可接受默认值。", file=output)

    config = InitConfig(
        name=prompt_value("项目名称", defaults.name, input_func),
        version=prompt_value("版本", defaults.version, input_func),
        description=prompt_value("描述", defaults.description, input_func),
        requires_python=prompt_value("Python 版本要求", defaults.requires_python, input_func),
        author_name=prompt_value("作者", defaults.author_name, input_func),
        author_email=prompt_value("邮箱", defaults.author_email, input_func),
        license_text=prompt_value("许可证", defaults.license_text, input_func),
    )
    validate_project_name(config.name)
    return config


def build_pyproject_content(config: InitConfig) -> str:
    """Build the pyproject.toml content for a new project."""
    validate_project_name(config.name)

    lines = [
        "[build-system]",
        'requires = ["setuptools>=61.0", "wheel"]',
        'build-backend = "setuptools.build_meta"',
        "",
        "[project]",
        f"name = {toml_string(config.name)}",
        f"version = {toml_string(config.version)}",
        f"description = {toml_string(config.description)}",
        'readme = "README.md"',
        f"requires-python = {toml_string(config.requires_python)}",
    ]

    if config.author_name or config.author_email:
        author_parts = []
        if config.author_name:
            author_parts.append(f"name = {toml_string(config.author_name)}")
        if config.author_email:
            author_parts.append(f"email = {toml_string(config.author_email)}")
        lines.extend([
            "authors = [",
            f"  {{ {', '.join(author_parts)} }}",
            "]",
        ])

    if config.license_text:
        lines.append(f"license = {{ text = {toml_string(config.license_text)} }}")

    lines.extend([
        "dependencies = []",
        "",
        *build_scripts_section(DEFAULT_QPIP_SCRIPTS),
    ])
    return "\n".join(lines) + "\n"


def build_readme_content(config: InitConfig) -> str:
    """Create a minimal README for new projects."""
    lines = [f"# {config.name}"]
    if config.description:
        lines.extend(["", config.description])
    return "\n".join(lines) + "\n"


def parse_init_args(args: Sequence[str]) -> bool:
    """Parse qpip init specific flags."""
    yes = False

    for arg in args:
        if arg in {"-y", "--yes"}:
            yes = True
            continue
        raise InitUsageError(f"init 不支持参数: {arg}")

    return yes


def confirm_init(input_func: PromptFunc) -> bool:
    """Ask the user whether to write the generated files."""
    answer = input_func("确认写入 pyproject.toml 吗？([Y]/n): ").strip().lower()
    return answer in {"", "y", "yes"}


def run_init_command(
    args: Sequence[str],
    *,
    cwd: Optional[Path] = None,
    env: Optional[Mapping[str, str]] = None,
    input_func: PromptFunc = input,
    output: Optional[TextIO] = None,
    dry_run: bool = False,
) -> int:
    """Handle `qpip init` in a npm-init-like style."""
    if not args or args[0] != "init":
        raise InitUsageError("init 命令格式无效")

    target_dir = Path.cwd() if cwd is None else cwd
    out = output if output is not None else os.sys.stdout
    yes = parse_init_args(args[1:])
    pyproject_path = target_dir / "pyproject.toml"

    if pyproject_path.exists():
        raise InitUsageError("当前目录已存在 pyproject.toml，已取消初始化")

    defaults = build_init_defaults(target_dir, env=env)

    try:
        config = defaults if yes else prompt_init_config(target_dir, defaults, input_func, out)
    except EOFError as exc:
        raise InitUsageError("未读取到完整输入，请重试或使用 qpip init -y") from exc

    content = build_pyproject_content(config)

    if dry_run:
        print("预览模式：不会写入文件。", file=out)
        print(content, file=out, end="")
        return 0

    if not yes:
        print("", file=out)
        print("将写入以下 pyproject.toml：", file=out)
        print(content, file=out, end="")
        if not confirm_init(input_func):
            print("已取消初始化。", file=out)
            return 1

    pyproject_path.write_text(content, encoding="utf-8")
    created_files = ["pyproject.toml"]

    readme_path = target_dir / "README.md"
    if not readme_path.exists():
        readme_path.write_text(build_readme_content(config), encoding="utf-8")
        created_files.append("README.md")

    print(f"已创建 {', '.join(created_files)}", file=out)
    return 0