from __future__ import annotations

import os
import stat
import tempfile
import unittest
from pathlib import Path
from types import SimpleNamespace
from unittest import mock

from droppass_cli import launcher


class DetectTargetTests(unittest.TestCase):
    def test_detect_target_linux_x64(self) -> None:
        with (
            mock.patch.object(launcher.sys, "platform", "linux"),
            mock.patch.object(launcher.platform, "machine", return_value="x86_64"),
        ):
            target = launcher.detect_target()

        self.assertEqual(target, launcher.TARGETS["linux-x64"])

    def test_detect_target_linux_arm64(self) -> None:
        with (
            mock.patch.object(launcher.sys, "platform", "linux"),
            mock.patch.object(launcher.platform, "machine", return_value="aarch64"),
        ):
            target = launcher.detect_target()

        self.assertEqual(target, launcher.TARGETS["linux-arm64"])

    def test_detect_target_windows_x64(self) -> None:
        with (
            mock.patch.object(launcher.sys, "platform", "win32"),
            mock.patch.object(launcher.platform, "machine", return_value="AMD64"),
        ):
            target = launcher.detect_target()

        self.assertEqual(target, launcher.TARGETS["win32-x64"])

    def test_detect_target_unsupported_platform_raises(self) -> None:
        with (
            mock.patch.object(launcher.sys, "platform", "linux"),
            mock.patch.object(launcher.platform, "machine", return_value="ppc64le"),
        ):
            with self.assertRaisesRegex(RuntimeError, "Unsupported platform linux-ppc64le"):
                launcher.detect_target()


class ResolveBinaryPathTests(unittest.TestCase):
    def test_resolve_binary_path_sets_execute_bit(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            package_root = Path(temp_dir)
            bin_dir = package_root / "bin"
            bin_dir.mkdir(parents=True)
            binary_path = bin_dir / "droppass-linux-x64"
            binary_path.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
            binary_path.chmod(0o644)

            with (
                mock.patch.object(launcher, "detect_target", return_value=launcher.TARGETS["linux-x64"]),
                mock.patch.object(launcher.resources, "files", return_value=package_root),
            ):
                resolved = launcher.resolve_binary_path()

            self.assertEqual(resolved, os.fspath(binary_path))
            self.assertTrue(os.stat(resolved).st_mode & stat.S_IXUSR)

    def test_resolve_binary_path_missing_binary_raises(self) -> None:
        with tempfile.TemporaryDirectory() as temp_dir:
            package_root = Path(temp_dir)

            with (
                mock.patch.object(launcher, "detect_target", return_value=launcher.TARGETS["linux-x64"]),
                mock.patch.object(launcher.resources, "files", return_value=package_root),
            ):
                with self.assertRaisesRegex(RuntimeError, "Missing embedded binary droppass-linux-x64"):
                    launcher.resolve_binary_path()


class MainTests(unittest.TestCase):
    def test_main_invokes_embedded_binary_with_args(self) -> None:
        completed = SimpleNamespace(returncode=0)

        with (
            mock.patch.object(launcher, "resolve_binary_path", return_value="/tmp/droppass"),
            mock.patch.object(launcher.subprocess, "run", return_value=completed) as run_mock,
        ):
            exit_code = launcher.main(["version"])

        run_mock.assert_called_once_with(["/tmp/droppass", "version"])
        self.assertEqual(exit_code, 0)

    def test_main_forwards_signal_exit(self) -> None:
        completed = SimpleNamespace(returncode=-15)

        with (
            mock.patch.object(launcher, "resolve_binary_path", return_value="/tmp/droppass"),
            mock.patch.object(launcher.subprocess, "run", return_value=completed),
            mock.patch.object(launcher.os, "getpid", return_value=1234),
            mock.patch.object(launcher.os, "kill") as kill_mock,
        ):
            exit_code = launcher.main(["version"])

        kill_mock.assert_called_once_with(1234, 15)
        self.assertEqual(exit_code, -15)
