"""
Comprehensive tests for rhoshift.utils.utils module.
"""

import os
import subprocess
import tempfile
from unittest.mock import Mock, call, mock_open, patch

import pytest

from rhoshift.utils.utils import (
    apply_manifest,
    run_command,
    wait_for_resource_for_specific_status,
)


class TestRunCommand:
    """Test cases for run_command function"""

    @patch("subprocess.run")
    def test_run_command_success(self, mock_run):
        """Test successful command execution"""
        mock_run.return_value = Mock(returncode=0, stdout="success output", stderr="")

        rc, stdout, stderr = run_command('echo "test"')

        assert rc == 0
        assert stdout == "success output"
        assert stderr == ""
        mock_run.assert_called_once()

    @patch("subprocess.run")
    def test_run_command_failure(self, mock_run):
        """Test failed command execution"""
        mock_run.return_value = Mock(returncode=1, stdout="", stderr="error message")

        rc, stdout, stderr = run_command("false")

        assert rc == 1
        assert stdout == ""
        assert stderr == "error message"

    @patch("subprocess.run")
    def test_run_command_with_timeout(self, mock_run):
        """Test command execution with timeout"""
        mock_run.return_value = Mock(returncode=0, stdout="output", stderr="")

        rc, stdout, stderr = run_command('echo "test"', timeout=30)

        assert rc == 0
        call_args = mock_run.call_args
        assert call_args[1]["timeout"] == 30

    @patch("subprocess.run")
    def test_run_command_timeout_exception(self, mock_run):
        """Test command execution timeout exception"""
        mock_run.side_effect = subprocess.TimeoutExpired("cmd", 30)

        rc, stdout, stderr = run_command("sleep 60", timeout=30)

        assert rc == 124  # Timeout exit code
        assert "Command timed out" in stderr

    @patch("subprocess.run")
    def test_run_command_with_retries(self, mock_run):
        """Test command execution with retries"""
        # First two calls fail, third succeeds
        mock_run.side_effect = [
            Mock(returncode=1, stdout="", stderr="error1"),
            Mock(returncode=1, stdout="", stderr="error2"),
            Mock(returncode=0, stdout="success", stderr=""),
        ]

        rc, stdout, stderr = run_command(
            "flaky-command", max_retries=3, retry_delay=0.1
        )

        assert rc == 0
        assert stdout == "success"
        assert mock_run.call_count == 3

    @patch("subprocess.run")
    def test_run_command_all_retries_fail(self, mock_run):
        """Test command execution when all retries fail"""
        mock_run.return_value = Mock(returncode=1, stdout="", stderr="persistent error")

        rc, stdout, stderr = run_command(
            "failing-command", max_retries=3, retry_delay=0.1
        )

        assert rc == 1
        assert stderr == "persistent error"
        assert mock_run.call_count == 3

    @patch("subprocess.run")
    @patch("logging.getLogger")
    def test_run_command_logging(self, mock_logger, mock_run):
        """Test command execution with logging"""
        logger_instance = Mock()
        mock_logger.return_value = logger_instance

        mock_run.return_value = Mock(returncode=0, stdout="output", stderr="")

        run_command('echo "test"', log_output=True)

        # Verify logging was attempted
        assert logger_instance.debug.called or logger_instance.info.called

    @patch("subprocess.run")
    def test_run_command_exception_handling(self, mock_run):
        """Test command execution exception handling"""
        mock_run.side_effect = Exception("Unexpected error")

        rc, stdout, stderr = run_command("bad-command")

        assert rc != 0
        assert "Unexpected error" in stderr


class TestApplyManifest:
    """Test cases for apply_manifest function"""

    @patch("rhoshift.utils.utils.run_command")
    def test_apply_manifest_success(self, mock_run_command):
        """Test successful manifest application"""
        mock_run_command.return_value = (0, "applied", "")

        result = apply_manifest("test: manifest")

        assert result == (0, "applied", "")
        mock_run_command.assert_called_once()

        # Check that the command includes the manifest
        call_args = mock_run_command.call_args[0][0]
        assert "oc apply -f -" in call_args

    @patch("rhoshift.utils.utils.run_command")
    def test_apply_manifest_failure(self, mock_run_command):
        """Test failed manifest application"""
        mock_run_command.return_value = (1, "", "apply failed")

        with pytest.raises(Exception) as excinfo:
            apply_manifest("invalid: manifest")

        assert "Manifest application failed" in str(excinfo.value)

    @patch("rhoshift.utils.utils.run_command")
    def test_apply_manifest_with_retries(self, mock_run_command):
        """Test manifest application with retries"""
        # First call fails, second succeeds
        mock_run_command.side_effect = [(1, "", "temporary error"), (0, "applied", "")]

        result = apply_manifest("test: manifest", max_retries=2, retry_delay=0.1)

        assert result == (0, "applied", "")
        assert mock_run_command.call_count == 2

    @patch("rhoshift.utils.utils.run_command")
    def test_apply_manifest_custom_oc_binary(self, mock_run_command):
        """Test manifest application with custom oc binary"""
        mock_run_command.return_value = (0, "applied", "")

        apply_manifest("test: manifest", oc_binary="/custom/oc")

        call_args = mock_run_command.call_args[0][0]
        assert "/custom/oc apply -f -" in call_args

    @patch("rhoshift.utils.utils.run_command")
    def test_apply_manifest_with_timeout(self, mock_run_command):
        """Test manifest application with timeout"""
        mock_run_command.return_value = (0, "applied", "")

        apply_manifest("test: manifest", timeout=120)

        call_args = mock_run_command.call_args
        assert call_args[1]["timeout"] == 120


class TestWaitForResourceForSpecificStatus:
    """Test cases for wait_for_resource_for_specific_status function"""

    @patch("rhoshift.utils.utils.run_command")
    @patch("time.sleep")
    def test_wait_for_resource_success(self, mock_sleep, mock_run_command):
        """Test waiting for resource with successful status"""
        mock_run_command.return_value = (0, "Ready", "")

        success, output, error = wait_for_resource_for_specific_status(
            status="Ready",
            cmd='oc get pod test-pod -o jsonpath="{.status.phase}"',
            timeout=60,
            interval=5,
        )

        assert success is True
        assert output == "Ready"
        assert error == ""

    @patch("rhoshift.utils.utils.run_command")
    @patch("time.sleep")
    def test_wait_for_resource_timeout(self, mock_sleep, mock_run_command):
        """Test waiting for resource with timeout"""
        mock_run_command.return_value = (0, "Pending", "")

        success, output, error = wait_for_resource_for_specific_status(
            status="Ready",
            cmd='oc get pod test-pod -o jsonpath="{.status.phase}"',
            timeout=10,
            interval=5,
        )

        assert success is False
        assert output == "Pending"
        assert "timed out" in error.lower()

    @patch("rhoshift.utils.utils.run_command")
    @patch("time.sleep")
    def test_wait_for_resource_case_sensitive(self, mock_sleep, mock_run_command):
        """Test waiting for resource with case sensitivity"""
        mock_run_command.return_value = (0, "ready", "")

        # Case sensitive - should fail
        success, output, error = wait_for_resource_for_specific_status(
            status="Ready",
            cmd='oc get pod test-pod -o jsonpath="{.status.phase}"',
            timeout=10,
            interval=5,
            case_sensitive=True,
        )

        assert success is False
        assert output == "ready"

    @patch("rhoshift.utils.utils.run_command")
    @patch("time.sleep")
    def test_wait_for_resource_case_insensitive(self, mock_sleep, mock_run_command):
        """Test waiting for resource without case sensitivity"""
        mock_run_command.return_value = (0, "ready", "")

        # Case insensitive - should succeed
        success, output, error = wait_for_resource_for_specific_status(
            status="Ready",
            cmd='oc get pod test-pod -o jsonpath="{.status.phase}"',
            timeout=60,
            interval=5,
            case_sensitive=False,
        )

        assert success is True
        assert output == "ready"

    @patch("rhoshift.utils.utils.run_command")
    @patch("time.sleep")
    def test_wait_for_resource_command_failure(self, mock_sleep, mock_run_command):
        """Test waiting for resource when command fails"""
        mock_run_command.return_value = (1, "", "command failed")

        success, output, error = wait_for_resource_for_specific_status(
            status="Ready", cmd="oc get pod nonexistent-pod", timeout=10, interval=5
        )

        assert success is False
        assert error == "command failed"

    @patch("rhoshift.utils.utils.run_command")
    @patch("time.sleep")
    def test_wait_for_resource_eventual_success(self, mock_sleep, mock_run_command):
        """Test waiting for resource that eventually succeeds"""
        # First call returns Pending, second returns Ready
        mock_run_command.side_effect = [(0, "Pending", ""), (0, "Ready", "")]

        success, output, error = wait_for_resource_for_specific_status(
            status="Ready",
            cmd='oc get pod test-pod -o jsonpath="{.status.phase}"',
            timeout=60,
            interval=5,
        )

        assert success is True
        assert output == "Ready"
        assert mock_run_command.call_count == 2


class TestIntegrationScenarios:
    """Integration test scenarios combining multiple functions"""

    @patch("rhoshift.utils.utils.run_command")
    def test_full_operator_installation_workflow(self, mock_run_command):
        """Test a complete operator installation workflow"""
        # Mock successful responses for the workflow
        mock_run_command.side_effect = [
            (0, "namespace/test-ns created", ""),  # namespace creation
            (
                0,
                "subscription.operators.coreos.com/test-operator created",
                "",
            ),  # subscription
            (0, "Succeeded", ""),  # operator status check
        ]

        # Apply namespace manifest
        namespace_manifest = """
apiVersion: v1
kind: Namespace
metadata:
  name: test-ns
"""
        result = apply_manifest(namespace_manifest)
        assert result[0] == 0

        # Apply operator subscription
        operator_manifest = """
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  name: test-operator
  namespace: test-ns
spec:
  channel: stable
  name: test-operator
  source: redhat-operators
"""
        result = apply_manifest(operator_manifest)
        assert result[0] == 0

        # Wait for operator to be ready
        success, output, error = wait_for_resource_for_specific_status(
            status="Succeeded",
            cmd='oc get csv -n test-ns -o jsonpath="{.items[0].status.phase}"',
            timeout=300,
            interval=10,
        )
        assert success is True
        assert output == "Succeeded"
