# /// script
# dependencies = [
#   "toml",
#   "rich",
# ]
# ///

# To run the script use `uv run convert_pipfile_to_uv.py` (this way it automagically installs the dependencies)
import re
from pathlib import Path
from urllib.parse import urlparse, urlunparse

import toml
from rich import print
from rich.prompt import Confirm, Prompt

PIPFILE_TO_UV_DEP_NAMES = {
    "packages": "dependencies",
    "dev-packages": "dev-dependencies",
}


# Override toml.TomlEncoder.dump_list to add newline and indentation
def _dump_list(self, v):
    sep = ","
    indentation = 4

    NL = "\n"
    INDENT = " " * indentation

    s = (
        "["
        + NL
        + INDENT
        + (sep + NL + INDENT).join([str(self.dump_value(u)) for u in v])
        + (sep + NL)
        + "]"
    )
    return s


toml.TomlEncoder.dump_list = _dump_list


def strip_url_credentials(url: str) -> str:
    """
    Remove username and password from URL if present.
    """
    parsed = urlparse(url)
    # Create new netloc without credentials
    netloc = parsed.hostname
    if parsed.port:
        netloc = f"{netloc}:{parsed.port}"
    # Reconstruct URL without credentials
    clean_url = urlunparse(
        (
            parsed.scheme,
            netloc,
            parsed.path,
            parsed.params,
            parsed.query,
            parsed.fragment,
        )
    )
    return clean_url


def dump_custom_version(package: str, version: dict) -> str:
    """
    Handle custom version formats (e.g., git, ref, index).
    """
    if "extras" in version:
        extras_str = ",".join(version["extras"])
        package = f"{package}[{extras_str}]"

    if "git" in version:
        git_url = version["git"]
        ref = version.get("ref", "main")
        return f"{package} @ {git_url}@{ref}"

    if "version" in version:
        return f"{package}{version['version']}"

    return package


def convert_pipfile_to_pyproject(
    pipfile_path: Path,
    output_path: Path,
    project_name: str,
    project_version: str,
    project_description: str,
    python_version: str = "pipfile",
):
    # Load the Pipfile
    with open(pipfile_path) as pipfile:
        pipfile_data = toml.load(pipfile)

    pyproject_data = {
        "project": {
            "name": project_name,
            "version": project_version,
            "dependencies": [],
            "dev-dependencies": [],  # tmp for easier map
        },
        "tool": {"uv": {"dev-dependencies": [], "sources": {}}},
    }

    if project_description:
        pyproject_data["project"]["description"] = project_description

    if python_version == "pipfile" and "python_version" in (
        pipfile_requires := pipfile_data.get("requires", {})
    ):
        python_version = pipfile_requires["python_version"]

    if python_version:
        python_version = (
            f">={python_version}" if python_version[0].isdigit() else python_version
        )
        pyproject_data["project"]["requires-python"] = python_version

    # Handle sources from Pipfile
    if "source" in pipfile_data:
        pyproject_data["tool"]["uv"]["index"] = []
        for source in pipfile_data["source"]:
            # Create source entry with cleaned URL (no credentials)
            source_entry = {
                "name": source["name"].replace("-", "_"),
                "url": strip_url_credentials(source["url"]),
            }
            pyproject_data["tool"]["uv"]["index"].append(source_entry)

    # Track packages with custom index
    packages_with_index = {}

    # Convert dependencies and handle index-specific packages
    for pipfile_name, uv_name in PIPFILE_TO_UV_DEP_NAMES.items():
        if pipfile_name in pipfile_data:
            for package, version in pipfile_data[pipfile_name].items():
                if isinstance(version, str):
                    if version == "*":
                        pyproject_data["project"][uv_name].append(f"{package}")
                    else:
                        pyproject_data["project"][uv_name].append(f"{package}{version}")
                elif isinstance(version, dict):
                    # Check for index specification
                    if "index" in version:
                        packages_with_index[package] = {"index": version["index"]}

                    # Add custom version formats
                    pyproject_data["project"][uv_name].append(
                        dump_custom_version(package, version)
                    )

    # Add packages with custom index to tool.uv.sources
    if packages_with_index:
        for package, index_info in packages_with_index.items():
            index_info["index"] = index_info["index"].replace("-", "_")
            pyproject_data["tool"]["uv"]["sources"][package] = index_info

    # moving dev-dependencies to tool.uv
    pyproject_data["tool"]["uv"]["dev-dependencies"] = pyproject_data["project"].pop(
        "dev-dependencies"
    )

    pyproject_data_str = toml.dumps(pyproject_data, encoder=toml.TomlEncoder())

    pyproject_data_str = transform_pyproject_data(pyproject_data_str)

    output_path.parent.mkdir(parents=True, exist_ok=True)
    with open(output_path, "w") as output_file:
        output_file.write(pyproject_data_str)

    print(f"[green]pyproject.toml generated at '{output_path}'[/green]")


def transform_pyproject_data(input_str: str) -> str:
    """
    Transforms the input string containing pyproject data by modifying sections
    that match the pattern "[tool.uv.sources.<something>]" and lines that start
    with "index =".
    Specifically, it performs the following transformations:
    1. For lines that match the pattern "[tool.uv.sources.<something>]", it changes
       them to "[tool.uv.sources]".
    2. For lines within the section that start with "index =", it extracts the value
       within the quotes and formats it as "<something> = {index = "<value>"}".
    Args:
        input_str (str): The input string containing pyproject data.
    Returns:
        str: The transformed pyproject data as a string.
    """

    lines = input_str.split("\n")

    transformed_lines = []
    current_section = None

    for line in lines:
        # Check if the line starts with the pattern "[tool.uv.sources."
        match = re.match(r"^\[tool\.uv\.sources\.(.*?)\]$", line.strip())
        if match:
            current_section = match.group(1)
            transformed_lines.append("[tool.uv.sources]")
        # Check if the line has the format "index = "<somethingB>""
        elif current_section and line.strip().startswith("index ="):
            index_value = re.search(r'"(.+?)"', line.strip()).group(1)
            transformed_lines.append(f'{current_section} = {{index = "{index_value}"}}')
            current_section = None
        else:
            transformed_lines.append(line.rstrip())

    return "\n".join(transformed_lines)


def get_input_with_default(prompt, default):
    user_input = Prompt.ask(prompt, default=default)
    return user_input if user_input else default


def main():
    print("[cyan]Welcome to the Pipfile to pyproject.toml converter![/cyan]")
    print("[cyan]Please provide the following information:[/cyan]")

    pipfile_path = None
    while not pipfile_path:
        pipfile_path = get_input_with_default(
            "Enter the path to your Pipfile: ", "Pipfile"
        )
        pipfile_path = Path(pipfile_path).resolve()
        if not pipfile_path.exists():
            print(f"[red]File not found: {pipfile_path}[/red]")
            pipfile_path = None

    output_path = get_input_with_default(
        "Enter the output path for pyproject.toml", "pyproject.toml"
    )
    output_path = Path(output_path).resolve()
    if output_path.exists():
        print(
            f"[red]CRITICAL WARNING: pyproject.toml file already exists at {output_path}[/red]"
        )
        if not Confirm.ask("Do you want to proceed?", default=True):
            print("[red]Exiting...[/red]")
            exit(1)

    # try find project name
    # 1. Pipfile parent directory name
    project_name = pipfile_path.parent.name

    project_name = get_input_with_default("Enter the project name", project_name)
    project_version = get_input_with_default("Enter the project version", "0.1.0")
    project_description = get_input_with_default("Enter the project description", "")
    python_version = get_input_with_default(
        "Enter the required Python version", "pipfile"
    )

    convert_pipfile_to_pyproject(
        pipfile_path,
        output_path,
        project_name,
        project_version,
        project_description,
        python_version,
    )


if __name__ == "__main__":
    main()