"""Unit tests for the permission fixer."""

from unittest.mock import MagicMock, patch

import pytest

from src.services.shared.permission_fixer import (
    PermissionFixError,
    apply_mount_permissions,
    apply_ssh_permissions,
    check_root_user_available,
    _run_in_container,
)


class TestApplyMountPermissions:
    """Tests for apply_mount_permissions."""

    @patch("src.services.shared.permission_fixer._run_in_container")
    def test_applies_chown_when_owner_declared(self, mock_run) -> None:
        mounts = [
            {"name": "workspace", "target": "/workspace", "owner": "user"},
        ]
        results = apply_mount_permissions("abc123", mounts)

        assert len(results) == 1
        assert results[0]["mount_name"] == "workspace"
        assert results[0]["success"] is True
        mock_run.assert_called_once()
        args = mock_run.call_args[0]
        assert args[0] == "abc123"
        assert args[1] == ["chown", "-R", "user:user", "/workspace"]

    @patch("src.services.shared.permission_fixer._run_in_container")
    def test_applies_chmod_when_mode_declared(self, mock_run) -> None:
        mounts = [
            {"name": "ssh", "target": "/home/user/.ssh", "mode": "0700"},
        ]
        results = apply_mount_permissions("abc123", mounts)

        assert results[0]["success"] is True
        # Only chmod called (no owner, so no chown)
        assert mock_run.call_count == 1
        chmod_call = mock_run.call_args_list[0]
        assert chmod_call[0][1] == ["chmod", "0700", "/home/user/.ssh"]

    @patch("src.services.shared.permission_fixer._run_in_container")
    def test_applies_file_mode_when_declared(self, mock_run) -> None:
        mounts = [
            {
                "name": "ssh",
                "target": "/home/user/.ssh",
                "file_mode": "0600",
            },
        ]
        results = apply_mount_permissions("abc123", mounts)

        assert results[0]["success"] is True
        # Only file_mode called (no owner, no mode)
        assert mock_run.call_count == 1
        file_mode_call = mock_run.call_args_list[0]
        assert file_mode_call[0][1][0] == "sh"
        assert (
            "find /home/user/.ssh -type f -exec chmod 0600" in file_mode_call[0][1][2]
        )

    @patch("src.services.shared.permission_fixer._run_in_container")
    def test_skips_readonly_mount(self, mock_run) -> None:
        mounts = [
            {
                "name": "ssh_keys",
                "target": "/home/user/.ssh",
                "readonly": True,
                "mode": "0700",
                "file_mode": "0600",
            },
        ]
        results = apply_mount_permissions("abc123", mounts)

        assert len(results) == 1
        assert results[0]["mount_name"] == "ssh_keys"
        assert results[0]["success"] is True
        mock_run.assert_not_called()

    @patch("src.services.shared.permission_fixer._run_in_container")
    def test_skips_mount_with_no_policy(self, mock_run) -> None:
        mounts = [
            {"name": "workspace", "target": "/workspace", "writable": True},
        ]
        results = apply_mount_permissions("abc123", mounts)

        assert len(results) == 1
        assert results[0]["success"] is True
        mock_run.assert_not_called()

    @patch("src.services.shared.permission_fixer._run_in_container")
    def test_reports_failure_on_command_error(self, mock_run) -> None:
        mock_run.side_effect = PermissionFixError("chown failed")

        mounts = [
            {"name": "workspace", "target": "/workspace", "owner": "user"},
        ]
        results = apply_mount_permissions("abc123", mounts)

        assert results[0]["success"] is False
        assert "chown failed" in results[0]["error"]

    @patch("src.services.shared.permission_fixer._run_in_container")
    def test_stops_on_first_failure(self, mock_run) -> None:
        """If chown fails, chmod and file_mode should not run."""
        mock_run.side_effect = PermissionFixError("chown failed")

        mounts = [
            {
                "name": "workspace",
                "target": "/workspace",
                "owner": "user",
                "mode": "0755",
                "file_mode": "0644",
            },
        ]
        results = apply_mount_permissions("abc123", mounts)

        assert results[0]["success"] is False
        assert mock_run.call_count == 1  # Only chown attempted


class TestRunInContainer:
    """Tests for _run_in_container."""

    @patch("subprocess.run")
    def test_success(self, mock_run) -> None:
        mock_run.return_value = MagicMock(returncode=0, stderr="")
        _run_in_container("abc123", ["echo", "hello"], 10)
        mock_run.assert_called_once()
        cmd = mock_run.call_args[0][0]
        assert cmd == ["docker", "exec", "--user", "root", "abc123", "echo", "hello"]

    @patch("subprocess.run")
    def test_failure_raises(self, mock_run) -> None:
        mock_run.return_value = MagicMock(returncode=1, stderr="permission denied")
        with pytest.raises(PermissionFixError, match="permission denied"):
            _run_in_container("abc123", ["chown", "x"], 10)

    @patch("subprocess.run")
    def test_timeout_raises(self, mock_run) -> None:
        import subprocess

        mock_run.side_effect = subprocess.TimeoutExpired(cmd=["docker"], timeout=10)
        with pytest.raises(PermissionFixError, match="timed out"):
            _run_in_container("abc123", ["chown", "x"], 10)


class TestApplySshPermissions:
    """Tests for apply_ssh_permissions."""

    @patch("subprocess.run")
    def test_applies_chown_chmod_and_file_mode(self, mock_run) -> None:
        mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
        result = apply_ssh_permissions("abc123", "/home/user/.ssh", "user")

        assert result["success"] is True
        # 3 fix commands + 3 verification commands
        assert mock_run.call_count == 6
        chown_cmd = mock_run.call_args_list[0][0][0]
        chmod_cmd = mock_run.call_args_list[1][0][0]
        file_mode_cmd = mock_run.call_args_list[2][0][0]

        assert chown_cmd == [
            "docker",
            "exec",
            "--user",
            "root",
            "abc123",
            "chown",
            "-R",
            "user:user",
            "/home/user/.ssh",
        ]
        assert chmod_cmd == [
            "docker",
            "exec",
            "--user",
            "root",
            "abc123",
            "chmod",
            "700",
            "/home/user/.ssh",
        ]
        assert file_mode_cmd[0] == "docker"
        assert (
            "find /home/user/.ssh -name 'id_*' -type f -exec chmod 600"
            in file_mode_cmd[-1]
        )

    @patch("subprocess.run")
    def test_uses_root_user(self, mock_run) -> None:
        mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
        result = apply_ssh_permissions("abc123", "/root/.ssh", "root")

        assert result["success"] is True
        chown_cmd = mock_run.call_args_list[0][0][0]
        assert chown_cmd == [
            "docker",
            "exec",
            "--user",
            "root",
            "abc123",
            "chown",
            "-R",
            "root:root",
            "/root/.ssh",
        ]

    @patch("subprocess.run")
    def test_reports_failure(self, mock_run) -> None:
        mock_run.return_value = MagicMock(
            returncode=1, stdout="", stderr="chown failed"
        )

        result = apply_ssh_permissions("abc123", "/home/user/.ssh", "user")

        assert result["success"] is False
        assert "chown failed" in result["error"]


class TestCheckRootUserAvailable:
    """Tests for check_root_user_available."""

    @patch("src.services.shared.permission_fixer._run_in_container")
    def test_returns_true_when_root_exists(self, mock_run) -> None:
        assert check_root_user_available("abc123") is True

    @patch("src.services.shared.permission_fixer._run_in_container")
    def test_returns_false_when_root_missing(self, mock_run) -> None:
        mock_run.side_effect = PermissionFixError("no such user")
        assert check_root_user_available("abc123") is False
