import subprocess
import sys
from pathlib import Path
from typing import cast

from .color import Colorizer
from .fp import lmap

type CommandList = list[str | Path | int | float] | list[str]


color = Colorizer()


def resolve_variables(
    commands: CommandList, env: dict[str, str | float | int | Path]
) -> CommandList:
    def get_var(s: str) -> str | float | int | Path:
        if s.startswith("$"):
            print(env.get(s[1:], ""))
            return env.get(s[1:], "")
        return s

    print(lmap(get_var, commands))
    return lmap(get_var, commands)  # type: ignore


def run(commands: CommandList, **kwargs) -> subprocess.CompletedProcess[bytes]:
    _kwargs = cast(dict, dict(check=True, capture_output=True) | kwargs)
    if "resolve" in _kwargs:
        del _kwargs["resolve"]
    return subprocess.run(list(map(str, commands)), **_kwargs)  # noqa: PLW1510


def capture(commands: CommandList, **kwargs) -> str:
    _commands: CommandList = commands
    _kwargs = cast(dict, dict(capture_output=True, check=False) | kwargs)
    if "resolve" in _kwargs:
        del _kwargs["resolve"]
        _commands = resolve_variables(commands, _kwargs.get("env", {}))
    return subprocess.run(list(map(str, _commands)), **_kwargs).stdout.decode().strip()  # noqa: PLW1510


def run_with_result(
    commands: CommandList,
    print_output: bool = False,
    fail_on_error: bool = True,
    **kwargs,
) -> tuple[bool, str]:
    _commands: CommandList = commands
    _kwargs = dict(check=True, capture_output=True) | kwargs  # type: ignore
    if "resolve" in _kwargs:
        del _kwargs["resolve"]
        _commands = resolve_variables(commands, _kwargs.get("env", {}))  # type: ignore
    try:
        subproc_result = subprocess.run(list(map(str, _commands)), **_kwargs)  # type: ignore  # noqa: PLW1510
        result = not bool(subproc_result.returncode)
        stdout = subproc_result.stdout.decode().strip()
        stdout = stdout + "\n" * bool(stdout)
        stderr = subproc_result.stderr.decode().strip()
        stderr = stderr + "\n" * bool(stdout)
    except Exception as e:
        stdout = ""
        stderr = str(e)
        result = False

    bar = "-" * 36
    topbar = f"{'=' * 36} {color.blue('COMMAND')} {'=' * 35}"
    command_literal = " ".join(list(map(str, _commands)))
    msg = (
        f"\n{topbar}\n{color.blue(command_literal)}"
        f"\n\n{bar} {color.green('STDOUT')} {bar}\n{stdout}\n{bar}"
        f" {color.red('STDERR')} {bar}\n{stderr}\n"
    )
    if fail_on_error and not result:
        print(color.red(f"\nCommand failed: {command_literal}'"))
        print(msg)
        sys.exit(1)
    if print_output:
        print()
        print(msg)
    return result, msg
