"""
The responsibility of this file and the similar ones under each command, is to translate the logic of the bmde interface
into the specification of the pure logic of the command, for example, translating shell into a corresponding entrypoint
"""

from __future__ import annotations

from pathlib import Path
from subprocess import Popen

from bmde.core import logging
from bmde.core.exec import ExecOptions

from ...config.loader import load_settings
from ...core.file_utils import resolve_elf, resolve_nds
from ...core.logging import configure_logging_from_settings
from ...core.spec_opts import SpecExecOpts
from ...core.types import (
    DOCKER_DESMUME_DEBUG_NETWORK,
    BackendOptions,
    DockerOutputOptions,
)
from ..run.service import RunService
from ..run.spec import RunSpec, RunSpecOpts
from .service import DebugService
from .settings import DebugSettings
from .spec import DebugSpec, DebugSpecOpts

log = logging.get_logger(__name__)


def create_debug_spec(
    nds: Path | None,
    elf: Path | None,
    arguments: list[str] | None = None,
    backend: BackendOptions | None = None,
    background: bool = False,
    docker_screen: DockerOutputOptions | None = None,
    dry_run: bool = False,
    entrypoint: Path | None = None,
    port: int = 1024,
    settings: DebugSettings | None = None,
) -> DebugSpec:
    if settings is None:
        settings = DebugSettings()

    elf_resolved, _ = resolve_elf(elf, cwd=Path.cwd())
    nds_resolved, _ = resolve_nds(nds, cwd=Path.cwd())

    # Default args are prepended to CLI args
    passed_args: list[str] = []
    if arguments is not None:
        passed_args += arguments
    if settings.execution_settings.arguments is not None:
        passed_args += settings.execution_settings.arguments

    return DebugSpec(
        DebugSpecOpts=DebugSpecOpts(
            elf=elf_resolved,
            docker_screen=(
                docker_screen if docker_screen is not None else settings.docker_screen
            ),
            docker_network=(
                settings.docker_network
                if settings.docker_network
                else DOCKER_DESMUME_DEBUG_NETWORK
            ),
            RunSpec=RunSpec(
                RunSpecOpts=RunSpecOpts(
                    nds_rom=nds_resolved,
                    fat_image=settings.run.fat_image,
                    graphical_output=settings.run.graphical_output,
                    debug=True,
                    arm9_debug_port=(
                        port
                        if port is not None
                        else (
                            settings.run.arm9_debug_port
                            if settings.run.arm9_debug_port is not None
                            else 1024
                        )
                    ),
                    docker_network=(
                        settings.run.docker_network
                        if settings.run.docker_network
                        else DOCKER_DESMUME_DEBUG_NETWORK
                    ),
                ),
                SpecExecOpts=SpecExecOpts(
                    dry_run=dry_run,
                    backend=(
                        settings.run.execution_settings.backend
                        if settings.run.execution_settings.backend is not None
                        else BackendOptions.DOCKER
                    ),
                    background=True,
                    entrypoint=settings.run.execution_settings.entrypoint,
                    arguments=arguments,
                    interactive=False,
                ),
            ),
        ),
        SpecExecOpts=SpecExecOpts(
            backend=(
                backend
                if backend is not None
                else (
                    settings.execution_settings.backend
                    if settings.execution_settings.backend is not None
                    else BackendOptions.DOCKER
                )
            ),
            entrypoint=(
                entrypoint
                if entrypoint is not None
                else settings.execution_settings.entrypoint
            ),
            arguments=passed_args,
            dry_run=(
                dry_run if dry_run is not None else settings.execution_settings.dry_run
            ),
            background=(
                background
                if background is not None
                else settings.execution_settings.background
            ),
            interactive=False,
        ),
    )


def execute_debug(spec: DebugSpec) -> int | Popen[bytes]:
    handle = RunService().run(
        spec.DebugSpecOpts.RunSpec.RunSpecOpts,
        ExecOptions(
            dry_run=spec.DebugSpecOpts.RunSpec.SpecExecOpts.dry_run,
            background=spec.DebugSpecOpts.RunSpec.SpecExecOpts.background,
            backend=spec.DebugSpecOpts.RunSpec.SpecExecOpts.backend,
            entrypoint=spec.DebugSpecOpts.RunSpec.SpecExecOpts.entrypoint,
            arguments=spec.DebugSpecOpts.RunSpec.SpecExecOpts.arguments,
        ),
    )

    code = DebugService().run(
        spec.DebugSpecOpts,
        ExecOptions(
            dry_run=spec.SpecExecOpts.dry_run,
            background=spec.SpecExecOpts.background,
            entrypoint=spec.SpecExecOpts.entrypoint,
            arguments=spec.SpecExecOpts.arguments,
            backend=spec.SpecExecOpts.backend,
            interactive=spec.SpecExecOpts.interactive,
        ),
    )

    if isinstance(handle, Popen):
        handle.communicate()

    return code


def debug_command(
    nds: Path | None,
    elf: Path | None,
    arguments: list[str] | None = None,
    backend: BackendOptions | None = None,
    background: bool = False,
    docker_screen: DockerOutputOptions | None = None,
    dry_run: bool = False,
    entrypoint: Path | None = None,
    port: int = 1024,
    settings: DebugSettings | None = None,
) -> int | Popen[bytes]:

    if settings is None:
        full_settings = load_settings()
        configure_logging_from_settings(settings=full_settings)
        settings = full_settings.debug

    log.debug("Settings for debug command:\n" f"- {str(settings)}\n")

    spec = create_debug_spec(
        nds=nds,
        elf=elf,
        arguments=arguments,
        backend=backend,
        background=background,
        docker_screen=docker_screen,
        dry_run=dry_run,
        entrypoint=entrypoint,
        port=port,
        settings=settings,
    )

    log.debug("Spec for debug command:\n" f"- {str(settings)}\n")

    return execute_debug(spec)
