from pathlib import Path
from typing import Any
from typing import Annotated
from uuid import UUID

import typer

from alloy_runtime_types.dtos.pipeline import UpdatePipelineRequest
from cli.commands.flag_utils import (
    require_exclusive,
    require_update_flags,
    validate_uuid,
)
from cli.commands.pipelines.get.presenter import present_pipeline
from cli.infrastructure.command import async_command, authenticated_client


def _validate_python_file(file: Path) -> None:
    if file.suffix != ".py":
        raise typer.BadParameter(
            f"File must have .py extension, got: {file.suffix}",
            param_hint="--file",
        )


def pipelines_update_command(
    pipeline_id: str = typer.Argument(..., help="Pipeline UUID"),
    name: Annotated[
        str | None, typer.Option("--name", "-n", help="New pipeline name")
    ] = None,
    description: Annotated[
        str | None, typer.Option("--description", "-d", help="New pipeline description")
    ] = None,
    clear_description: bool = typer.Option(
        False,
        "--clear-description",
        help="Clear the pipeline description",
    ),
    file: Path | None = typer.Option(
        None,
        "--file",
        "-f",
        help="Replacement Python file containing @pipeline decorated function",
        exists=True,
        readable=True,
    ),
    function_name: Annotated[
        str | None,
        typer.Option(
            "--function-name",
            help="Replacement @pipeline function name",
        ),
    ] = None,
    clear_function_name: bool = typer.Option(
        False,
        "--clear-function-name",
        help="Clear the explicit pipeline function name and use the first pipeline in the module",
    ),
    public: Annotated[
        bool | None,
        typer.Option(
            "--public/--private",
            help="Update whether the pipeline is publicly accessible",
        ),
    ] = None,
) -> None:
    pipeline_uuid = validate_uuid(pipeline_id, "pipeline")
    require_exclusive(
        ("--description", description), ("--clear-description", clear_description)
    )
    require_exclusive(
        ("--function-name", function_name),
        ("--clear-function-name", clear_function_name),
    )
    require_update_flags(
        ("--name", name),
        ("--description", description),
        ("--clear-description", clear_description),
        ("--file", file),
        ("--function-name", function_name),
        ("--clear-function-name", clear_function_name),
        ("--public/--private", public),
    )

    if file is not None:
        _validate_python_file(file)

    _execute_update(
        pipeline_id=pipeline_uuid,
        name=name,
        description=description,
        clear_description=clear_description,
        file=file,
        function_name=function_name,
        clear_function_name=clear_function_name,
        public=public,
    )


@async_command
async def _execute_update(
    pipeline_id: UUID,
    *,
    name: str | None,
    description: str | None,
    clear_description: bool,
    file: Path | None,
    function_name: str | None,
    clear_function_name: bool,
    public: bool | None,
) -> None:
    request_data: dict[str, Any] = {}
    if name is not None:
        request_data["name"] = name
    if description is not None or clear_description:
        request_data["description"] = None if clear_description else description
    if file is not None:
        module_content = file.read_text()
        if not module_content.strip():
            raise typer.BadParameter(
                "Pipeline file cannot be empty", param_hint="--file"
            )
        request_data["module_content"] = module_content
    if function_name is not None or clear_function_name:
        request_data["pipeline_function_name"] = (
            None if clear_function_name else function_name
        )
    if public is not None:
        request_data["is_public"] = public

    request = UpdatePipelineRequest(**request_data)

    async with authenticated_client() as (_config, client):
        response = await client.update_pipeline(pipeline_id, request)

    present_pipeline(response)
