import textwrap
from typing import Optional

import pytest

from debputy.highlevel_manifest import HighLevelManifest
from debputy.highlevel_manifest_parser import YAMLManifestParser
from debputy.interpreter import extract_shebang_interpreter
from debputy.plugin.api import virtual_path_def
from debputy.plugin.api.test_api import build_virtual_file_system
from debputy.transformation_rules import NormalizeShebangLineTransformation


@pytest.mark.parametrize(
    "raw_shebang,original_command,command_full_basename,command_stem,correct_command,corrected_shebang_line",
    [
        (
            b"#!       /usr/bin/false\r\n",
            "/usr/bin/false",
            "false",
            "false",
            None,
            None,
        ),
        (
            b"#!/usr/bin/python3 -b",
            "/usr/bin/python3",
            "python3",
            "python",
            "/usr/bin/python3",
            None,
        ),
        (
            b"#!/usr/bin/env python3 -b",
            "/usr/bin/env python3",
            "python3",
            "python",
            "/usr/bin/python3",
            "#! /usr/bin/python3 -b",
        ),
        (
            b"#! /bin/env python3.12-dbg -b",
            "/bin/env python3.12-dbg",
            "python3.12-dbg",
            "python",
            "/usr/bin/python3.12-dbg",
            "#! /usr/bin/python3.12-dbg -b",
        ),
        (
            b"#! /usr/bin/bash",
            "/usr/bin/bash",
            "bash",
            "bash",
            "/bin/bash",
            "#! /bin/bash",
        ),
        (
            b"#! /usr/bin/env sh",
            "/usr/bin/env sh",
            "sh",
            "sh",
            "/bin/sh",
            "#! /bin/sh",
        ),
        (
            b"#! /usr/local/bin/perl",
            "/usr/local/bin/perl",
            "perl",
            "perl",
            "/usr/bin/perl",
            "#! /usr/bin/perl",
        ),
    ],
)
def test_interpreter_detection(
    raw_shebang: bytes,
    original_command: str,
    command_full_basename: str,
    command_stem: str,
    correct_command: Optional[str],
    corrected_shebang_line: Optional[str],
) -> None:
    interpreter = extract_shebang_interpreter(raw_shebang)
    # The `and ...` part is just to get the raw line in the error message
    assert interpreter is not None

    assert interpreter.original_command == original_command
    assert interpreter.command_full_basename == command_full_basename
    assert interpreter.command_stem == command_stem
    assert interpreter.correct_command == correct_command
    assert interpreter.corrected_shebang_line == corrected_shebang_line
    assert interpreter.fixup_needed == (corrected_shebang_line is not None)


@pytest.mark.parametrize(
    "raw_data",
    [
        b"#!#!#!       /usr/bin/false",
        b"#!perl",  # Used in files as an editor hint
        b"\x7FELF/usr/bin/perl",
        b"\x00\01\x02\x03/usr/bin/perl",
        b"PK\x03\x03/usr/bin/perl",
    ],
)
def test_interpreter_negative(raw_data: bytes) -> None:
    assert extract_shebang_interpreter(raw_data) is None


@pytest.fixture
def empty_manifest(
    amd64_dpkg_architecture_variables,
    dpkg_arch_query,
    source_package,
    package_single_foo_arch_all_cxt_amd64,
    amd64_substitution,
    no_profiles_or_build_options,
    debputy_plugin_feature_set,
) -> HighLevelManifest:
    # We need an empty directory to avoid triggering packager provided files.
    debian_dir = build_virtual_file_system([])
    return YAMLManifestParser(
        "debian/test-debputy.manifest",
        source_package,
        package_single_foo_arch_all_cxt_amd64,
        amd64_substitution,
        amd64_dpkg_architecture_variables,
        dpkg_arch_query,
        no_profiles_or_build_options,
        debputy_plugin_feature_set,
        "full",
        debian_dir=debian_dir,
    ).build_manifest()


def test_interpreter_rewrite(empty_manifest: HighLevelManifest) -> None:
    condition_context = empty_manifest.condition_context("foo")
    fs_root = build_virtual_file_system(
        [
            virtual_path_def("usr/bin/foo", content="random data"),
            virtual_path_def(
                "usr/bin/foo.sh",
                materialized_content="#!/usr/bin/sh\nset -e\n",
            ),
        ]
    )
    interpreter_normalization = NormalizeShebangLineTransformation()
    interpreter_normalization.transform_file_system(fs_root, condition_context)
    foo = fs_root.lookup("usr/bin/foo")
    foo_sh = fs_root.lookup("usr/bin/foo.sh")

    assert foo is not None and foo.is_file
    with foo.open() as fd:
        assert fd.read() == "random data"

    assert foo_sh is not None and foo_sh.is_file
    with foo_sh.open() as fd:
        expected = textwrap.dedent(
            """\
        #! /bin/sh
        set -e
        """
        )
        assert fd.read() == expected
