from __future__ import annotations

import importlib
import re
from pathlib import Path
from typing import Annotated, Any

import httpx
import tomlkit as toml
from typer import Argument, Option

from gradio.analytics import custom_component_analytics
from gradio.cli.commands.display import LivePanelDisplay

from ._docs_assets import css
from ._docs_utils import (
    RUFF_INSTALLED,
    extract_docstrings,
    get_deep,
    make_markdown,
    make_space,
)


def _docs(
    path: Annotated[
        Path, Argument(help="The directory of the custom component.")
    ] = Path("."),
    demo_dir: Annotated[Path | None, Option(help="Path to the demo directory.")] = None,
    demo_name: Annotated[str | None, Option(help="Name of the demo file.")] = None,
    readme_path: Annotated[
        Path | None, Option(help="Path to the README.md file.")
    ] = None,
    space_url: Annotated[
        str | None, Option(help="URL of the Space to use for the demo.")
    ] = None,
    generate_space: Annotated[
        bool,
        Option(
            help="Create a documentation space for the custom compone.", is_flag=True
        ),
    ] = True,
    generate_readme: Annotated[
        bool,
        Option(help="Create a README.md file for the custom component.", is_flag=True),
    ] = True,
    suppress_demo_check: Annotated[
        bool,
        Option(
            help="Suppress demo warnings and errors.",
            is_flag=True,
        ),
    ] = False,
):
    """Runs the documentation generator."""
    custom_component_analytics(
        "docs",
        None,
        None,
        None,
        None,
    )

    _component_dir = Path(path).resolve()
    _demo_dir = Path(demo_dir).resolve() if demo_dir else Path("demo").resolve()
    _demo_name = demo_name if demo_name else "app.py"
    _demo_path = _demo_dir / _demo_name
    _readme_path = (
        Path(readme_path).resolve() if readme_path else _component_dir / "README.md"
    )

    if not generate_space and not generate_readme:
        raise ValueError("Must generate at least one of space or readme")

    with LivePanelDisplay() as live:
        live.update(
            f":page_facing_up: Generating documentation for [orange3]{str(_component_dir.name)}[/]",
            add_sleep=0.2,
        )
        live.update(
            f":eyes: Reading project metadata from [orange3]{_component_dir}/pyproject.toml[/]\n"
        )

        if not (_component_dir / "pyproject.toml").exists():
            raise ValueError(
                f"Cannot find pyproject.toml file in [orange3]{_component_dir}[/]"
            )

        with open(_component_dir / "pyproject.toml", encoding="utf-8") as f:
            data = toml.loads(f.read())

        name = get_deep(data, ["project", "name"])

        if not isinstance(name, str):
            raise ValueError("Name not found in pyproject.toml")

        if not RUFF_INSTALLED:
            live.update(
                "\n:warning: [yellow]Ruff is not installed. Code snippets will not be formatted. To install, run `pip install ruff`.[/]"
            )

        run_command(
            live=live,
            name=name,
            suppress_demo_check=suppress_demo_check,
            pyproject_toml=data,
            generate_space=generate_space,
            generate_readme=generate_readme,
            type_mode="simple",
            _demo_path=_demo_path,
            _demo_dir=_demo_dir,
            _readme_path=_readme_path,
            space_url=space_url,
            _component_dir=_component_dir,
        )


def run_command(
    live: LivePanelDisplay,
    name: str,
    pyproject_toml: dict[str, Any],
    suppress_demo_check: bool,
    generate_space: bool,
    generate_readme: bool,
    type_mode: str,
    _demo_path: Path,
    _demo_dir: Path,
    _readme_path: Path,
    space_url: str | None,
    _component_dir: Path,
    simple: bool = False,
):
    with open(_demo_path, encoding="utf-8") as f:
        demo = f.read()

    pypi_exists = httpx.get(f"https://pypi.org/pypi/{name}/json").status_code

    pypi_exists = pypi_exists == 200 or False

    local_version = get_deep(pyproject_toml, ["project", "version"])
    description = str(get_deep(pyproject_toml, ["project", "description"]) or "")
    repo = get_deep(pyproject_toml, ["project", "urls", "repository"])
    space = (
        space_url
        if space_url
        else get_deep(pyproject_toml, ["project", "urls", "space"])
    )

    if not local_version and not pypi_exists:
        raise ValueError(
            f"Cannot find version in pyproject.toml or on PyPI for [orange3]{name}[/].\nIf you have just published to PyPI, please wait a few minutes and try again."
        )
    module = importlib.import_module(name)
    (docs, type_mode) = extract_docstrings(module)

    if generate_space:
        if not simple:
            live.update(":computer: [blue]Generating space.[/]")

        source = make_space(
            docs=docs,
            name=name,
            description=description,
            local_version=local_version
            if local_version is None
            else str(local_version),
            demo=demo,
            space=space if space is None else str(space),
            repo=repo if repo is None else str(repo),
            pypi_exists=pypi_exists,
            suppress_demo_check=suppress_demo_check,
        )

        with open(_demo_dir / "space.py", "w", encoding="utf-8") as f:
            f.write(source)
            if not simple:
                live.update(
                    f":white_check_mark: Space created in [orange3]{_demo_dir}/space.py[/]\n"
                )
        with open(_demo_dir / "css.css", "w", encoding="utf-8") as f:
            f.write(css)

    if generate_readme:
        if not simple:
            live.update(":pencil: [blue]Generating README.[/]")
        readme = make_markdown(
            docs, name, description, local_version, demo, space, repo, pypi_exists
        )

        readme_content = Path(_readme_path).read_text()

        with open(_readme_path, "w", encoding="utf-8") as f:
            yaml_regex = re.search(
                "(?:^|[\r\n])---[\n\r]+([\\S\\s]*?)[\n\r]+---([\n\r]|$)", readme_content
            )
            if yaml_regex is not None:
                readme = readme_content[: yaml_regex.span()[-1]] + readme
            f.write(readme)
            if not simple:
                live.update(
                    f":white_check_mark: README generated in [orange3]{_readme_path}[/]"
                )
    if simple:
        short_readme_path = Path(_readme_path).relative_to(_component_dir)
        short_demo_path = Path(_demo_dir / "space.py").relative_to(_component_dir)
        live.update(
            f":white_check_mark: Documentation generated in [orange3]{short_demo_path}[/] and [orange3]{short_readme_path}[/]. Pass --no-generate-docs to disable auto documentation."
        )

    if type_mode == "simple":
        live.update(
            "\n:orange_circle: [red]The docs were generated in simple mode. Updating python to a more recent version will result in richer documentation.[/]"
        )
