from pathlib import Path

from datapipeline.config.workspace import WorkspaceContext
from datapipeline.cli.workspace_utils import resolve_default_project_yaml
from datapipeline.services.paths import pkg_root
from datapipeline.services.project_paths import resolve_project_yaml_path
from datapipeline.services.scaffold.discovery import (
    list_domains,
    list_mappers,
    list_parsers,
    list_sources,
    list_dtos,
)
from datapipeline.services.scaffold.source_yaml import default_loader_config
from datapipeline.services.scaffold.layout import (
    default_stream_id,
    dto_class_name,
    default_parser_name,
    default_mapper_name,
    dto_module_path,
    LABEL_DTO_FOR_PARSER,
    LABEL_DTO_FOR_MAPPER,
    LABEL_DOMAIN_TO_MAP,
    LABEL_MAPPER_INPUT,
    default_mapper_name_for_identity,
)
from datapipeline.services.scaffold.stream_plan import StreamPlan, ParserPlan, MapperPlan, execute_stream_plan
from datapipeline.services.scaffold.utils import (
    choose_existing_or_create,
    choose_name,
    choose_existing_or_create_name,
    error_exit,
    info,
    pick_from_list,
    pick_from_menu,
    prompt_required,
)


def handle(*, plugin_root: Path | None = None, workspace: WorkspaceContext | None = None) -> None:
    root_dir, pkg_name, _ = pkg_root(plugin_root)
    project_yaml = resolve_default_project_yaml(workspace) or resolve_project_yaml_path(root_dir)

    # Shared context
    provider = prompt_required("Provider")
    dataset = prompt_required("Dataset")
    source_id = f"{provider}.{dataset}"

    # Collected actions (execute at end)
    create_source = False
    create_domain_flag = False
    create_parser_flag = False
    create_mapper_flag = False
    parser_create_dto = False
    mapper_create_dto = False

    dto_class = None
    dto_module = None
    mapper_input_class = None
    mapper_input_module = None
    loader_ep = None
    loader_args = None
    parser_ep = None
    mapper_ep = None
    parser_name = None
    mapper_name = None
    pchoice = "identity"

    # Source selection (may override shared context if existing is chosen)
    source_choice = pick_from_menu(
        "Source:",
        [
            ("create", "Create new source (default)"),
            ("existing", "Select existing source"),
        ],
    )

    if source_choice == "existing":
        sources = list_sources(project_yaml)
        if not sources:
            error_exit("No sources found. Create one first.")
        source_id = pick_from_list("Select source:", sources)
        parts = source_id.split(".", 1)
        provider = parts[0] if len(parts) == 2 else provider
        dataset = parts[1] if len(parts) == 2 else dataset
    else:
        source_id_default = f"{provider}.{dataset}"
        source_id = choose_name("Source id", default=source_id_default)
        create_source = True

        # Loader selection
        loader_ep = None
        loader_args = {}
        choice = pick_from_menu(
            "Loader:",
            [
                ("fs", "Built-in fs"),
                ("http", "Built-in http"),
                ("synthetic", "Built-in synthetic"),
                ("custom", "Custom loader"),
            ],
            allow_default=False,
        )
        if choice in {"fs", "http", "synthetic"}:
            if choice in {"fs", "http"}:
                fmt_options = [
                    ("csv", "csv"),
                    ("json", "json"),
                    ("json-lines", "json-lines"),
                ]
                if choice == "fs":
                    fmt_options.append(("pickle", "pickle"))
                fmt = pick_from_menu("Format:", fmt_options, allow_default=False)
            else:
                fmt = None
            loader_ep, loader_args = default_loader_config(choice, fmt)
        else:
            loader_ep = prompt_required("Loader entrypoint")

        # Parser selection
        parsers = list_parsers(root=plugin_root)
        if parsers:
            pchoice = pick_from_menu(
                "Parser:",
                [
                    ("create", "Create new parser (default)"),
                    ("existing", "Select existing parser"),
                    ("identity", "Identity parser"),
                ],
            )
        else:
            pchoice = pick_from_menu(
                "Parser:",
                [
                    ("create", "Create new parser (default)"),
                    ("identity", "Identity parser"),
                ],
            )
        if pchoice == "existing":
            parser_ep = pick_from_menu(
                "Select parser entrypoint:",
                [(k, k) for k in sorted(parsers.keys())],
            )
        elif pchoice == "create":
            dto_default = dto_class_name(f"{provider}_{dataset}") if provider and dataset else None
            dto_class, parser_create_dto = choose_existing_or_create_name(
                label=LABEL_DTO_FOR_PARSER,
                existing=sorted(list_dtos(root=plugin_root).keys()),
                create_label="Create new DTO",
                prompt_new="DTO class name",
                default_new=dto_default,
            )
            parser_name = choose_name(
                "Parser class name",
                default=default_parser_name(dto_class),
            )
            dto_module = dto_module_path(pkg_name, dto_class)
            create_parser_flag = True
        elif pchoice == "identity":
            parser_ep = "identity"
        else:
            parser_ep = "identity"

    # Domain selection
    domain, create_domain_flag = choose_existing_or_create_name(
        label=LABEL_DOMAIN_TO_MAP,
        existing=list_domains(root=plugin_root),
        create_label="Create new domain",
        prompt_new="Domain name",
        default_new=dataset,
    )

    # Mapper selection
    mappers = list_mappers(root=plugin_root)
    if mappers:
        mchoice = pick_from_menu(
            "Mapper:",
            [
                ("create", "Create new mapper (default)"),
                ("existing", "Select existing mapper"),
                ("identity", "Identity mapper"),
            ],
        )
    else:
        mchoice = pick_from_menu(
            "Mapper:",
            [
                ("create", "Create new mapper (default)"),
                ("identity", "Identity mapper"),
            ],
        )
    if mchoice == "existing":
        mapper_ep = pick_from_menu(
            "Select mapper entrypoint:",
            [(k, k) for k in sorted(mappers.keys())],
        )
    elif mchoice == "create":
        create_mapper_flag = True
        input_choice = pick_from_menu(
            f"{LABEL_MAPPER_INPUT}:",
            [
                ("dto", "DTO (default)"),
                ("identity", "Any"),
            ],
        )
        info("Domain output: Domain record")
        if input_choice == "dto":
            if not dto_class:
                dto_class, mapper_create_dto = choose_existing_or_create_name(
                    label=LABEL_DTO_FOR_MAPPER,
                    existing=sorted(list_dtos(root=plugin_root).keys()),
                    create_label="Create new DTO",
                    prompt_new="DTO class name",
                    default_new=dto_class_name(f"{provider}_{dataset}"),
                )
            else:
                mapper_create_dto = False
            dto_module = dto_module_path(pkg_name, dto_class)
            mapper_input_class = dto_class
            mapper_input_module = dto_module
        else:
            mapper_input_module = "typing"
            mapper_input_class = "Any"
            mapper_create_dto = False
        if input_choice == "identity":
            mapper_name = choose_name(
                "Mapper name",
                default=default_mapper_name_for_identity(domain),
            )
        else:
            mapper_name = choose_name("Mapper name", default=default_mapper_name(mapper_input_module, domain))
    elif mchoice == "identity":
        mapper_ep = "identity"
    else:
        mapper_ep = "identity"

    # Stream id and contract
    default_id = default_stream_id(domain, dataset or "dataset", None)
    stream_id = choose_name("Stream id", default=default_id)

    # Build plan and execute (no side effects during selection)
    parser_plan = None
    if pchoice == "create":
        parser_plan = ParserPlan(
            create=True,
            create_dto=parser_create_dto,
            dto_class=dto_class,
            dto_module=dto_module,
            parser_name=parser_name,
        )
    elif pchoice == "existing":
        parser_plan = ParserPlan(create=False, parser_ep=parser_ep)
    else:
        parser_plan = ParserPlan(create=False, parser_ep="identity")

    mapper_plan = None
    if mchoice == "create":
        mapper_plan = MapperPlan(
            create=True,
            create_dto=mapper_create_dto,
            input_class=mapper_input_class,
            input_module=mapper_input_module,
            mapper_name=mapper_name,
            domain=domain,
        )
    elif mchoice == "existing":
        mapper_plan = MapperPlan(create=False, mapper_ep=mapper_ep, domain=domain)
    else:
        mapper_plan = MapperPlan(create=False, mapper_ep="identity", domain=domain)

    plan = StreamPlan(
        provider=provider,
        dataset=dataset,
        source_id=source_id,
        project_yaml=project_yaml,
        stream_id=stream_id,
        root=plugin_root,
        create_source=create_source,
        loader_ep=loader_ep,
        loader_args=loader_args,
        parser=parser_plan,
        mapper=mapper_plan,
        domain=domain,
        create_domain=create_domain_flag,
    )
    execute_stream_plan(plan)
