#!/usr/bin/env python3
"""
Tests for the MsfExploitHandler.

Tests parsing accuracy and display functionality.
"""
import os
import tempfile
from unittest.mock import MagicMock, patch

import pytest

from souleyez.engine.job_status import STATUS_DONE, STATUS_NO_RESULTS, STATUS_WARNING
from souleyez.handlers.msf_exploit_handler import MsfExploitHandler


@pytest.fixture
def handler():
    """Provide a fresh handler instance."""
    return MsfExploitHandler()


@pytest.fixture
def mock_managers():
    """Mock database managers."""
    host_manager = MagicMock()
    findings_manager = MagicMock()
    credentials_manager = MagicMock()

    # Default: host exists
    host_manager.get_host_by_ip.return_value = {"id": 1}

    return {
        "host_manager": host_manager,
        "findings_manager": findings_manager,
        "credentials_manager": credentials_manager,
    }


class TestHandlerMetadata:
    """Test handler metadata."""

    def test_tool_name(self, handler):
        """Handler should have correct tool name."""
        assert handler.tool_name == "msf_exploit"

    def test_display_name(self, handler):
        """Handler should have human-readable display name."""
        assert handler.display_name == "Metasploit Exploit"

    def test_capability_flags(self, handler):
        """Handler should have all capability flags enabled."""
        assert handler.has_done_handler is True
        assert handler.has_warning_handler is True
        assert handler.has_error_handler is True
        assert handler.has_no_results_handler is True


class TestParseJobSuccess:
    """Test successful exploit parsing."""

    def test_meterpreter_session_opened(self, handler, mock_managers):
        """Meterpreter session opened should be detected as success."""
        log_content = """
[*] Started reverse TCP handler on 10.0.0.1:4444
[*] Sending stage (175174 bytes) to 10.0.0.5
[*] Meterpreter session 1 opened (10.0.0.1:4444 -> 10.0.0.5:49234) at 2024-01-15
"""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as f:
            f.write(log_content)
            log_path = f.name

        try:
            job = {"target": "10.0.0.5"}
            result = handler.parse_job(1, log_path, job, **mock_managers)

            assert result["status"] == STATUS_DONE
            assert result["success"] is True
            assert "successful" in result["summary"].lower()
        finally:
            os.unlink(log_path)

    def test_command_shell_session_opened(self, handler, mock_managers):
        """Command shell session opened should be detected as success."""
        log_content = """
[*] Command shell session 2 opened (10.0.0.1:4444 -> 10.0.0.5:12345)
"""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as f:
            f.write(log_content)
            log_path = f.name

        try:
            job = {"target": "10.0.0.5"}
            result = handler.parse_job(1, log_path, job, **mock_managers)

            assert result["status"] == STATUS_DONE
            assert result["success"] is True
        finally:
            os.unlink(log_path)

    def test_session_created_pattern(self, handler, mock_managers):
        """Session created pattern should be detected."""
        log_content = """
[+] 10.0.0.5:445 - Session 3 created in the background.
"""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as f:
            f.write(log_content)
            log_path = f.name

        try:
            job = {"target": "10.0.0.5"}
            result = handler.parse_job(1, log_path, job, **mock_managers)

            assert result["status"] == STATUS_DONE
            assert result["success"] is True
        finally:
            os.unlink(log_path)


class TestParseJobFailure:
    """Test failed exploit parsing."""

    def test_no_session_created_returns_warning(self, handler, mock_managers):
        """'No session was created' should return warning status."""
        log_content = """
[*] Started reverse TCP handler on 10.0.0.1:4444
[*] Sending exploit...
[-] Exploit completed, but no session was created.
"""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as f:
            f.write(log_content)
            log_path = f.name

        try:
            job = {"target": "10.0.0.5"}
            result = handler.parse_job(1, log_path, job, **mock_managers)

            assert result["status"] == STATUS_WARNING
            assert result["success"] is False
            assert result["explicit_failure"] is True
            assert "no session" in result["summary"].lower()
        finally:
            os.unlink(log_path)

    def test_connection_refused_returns_warning(self, handler, mock_managers):
        """Connection refused should return warning status."""
        log_content = """
[*] Started reverse TCP handler on 10.0.0.1:4444
[-] Connection refused
"""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as f:
            f.write(log_content)
            log_path = f.name

        try:
            job = {"target": "10.0.0.5"}
            result = handler.parse_job(1, log_path, job, **mock_managers)

            assert result["status"] == STATUS_WARNING
            assert result["success"] is False
            assert "refused" in result["summary"].lower()
        finally:
            os.unlink(log_path)

    def test_timeout_returns_warning(self, handler, mock_managers):
        """Timeout should return warning status."""
        log_content = """
[-] Exploit timed out
"""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as f:
            f.write(log_content)
            log_path = f.name

        try:
            job = {"target": "10.0.0.5"}
            result = handler.parse_job(1, log_path, job, **mock_managers)

            assert result["status"] == STATUS_WARNING
            assert result["explicit_failure"] is True
            assert "unreachable" in result["summary"].lower()
        finally:
            os.unlink(log_path)


class TestParseJobNoResults:
    """Test no results scenario."""

    def test_empty_output_returns_no_results(self, handler, mock_managers):
        """Empty or minimal output should return no_results."""
        log_content = """
[*] Starting exploit...
[*] Exploit completed.
"""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as f:
            f.write(log_content)
            log_path = f.name

        try:
            job = {"target": "10.0.0.5"}
            result = handler.parse_job(1, log_path, job, **mock_managers)

            assert result["status"] == STATUS_NO_RESULTS
            assert result["success"] is False
            assert result["explicit_failure"] is False
        finally:
            os.unlink(log_path)


class TestParseJobExploitName:
    """Test exploit name extraction."""

    def test_extracts_exploit_name_from_header(self, handler, mock_managers):
        """Should extract exploit name from 'Exploit:' header."""
        log_content = """
Exploit: exploit/unix/ftp/vsftpd_234_backdoor
[*] Starting exploit...
"""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as f:
            f.write(log_content)
            log_path = f.name

        try:
            job = {"target": "10.0.0.5"}
            result = handler.parse_job(1, log_path, job, **mock_managers)

            assert result["exploit"] == "exploit/unix/ftp/vsftpd_234_backdoor"
        finally:
            os.unlink(log_path)


class TestParseJobFindingsCreation:
    """Test that findings are created correctly."""

    def test_successful_exploit_creates_critical_finding(self, handler, mock_managers):
        """Successful exploit should create critical finding."""
        log_content = """
Exploit: exploit/unix/ftp/vsftpd_234_backdoor
[*] Meterpreter session 1 opened (10.0.0.1:4444 -> 10.0.0.5:21)
"""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".log", delete=False) as f:
            f.write(log_content)
            log_path = f.name

        try:
            job = {"target": "10.0.0.5"}
            result = handler.parse_job(1, log_path, job, **mock_managers)

            # Verify finding was added
            mock_managers["findings_manager"].add_finding.assert_called_once()
            call_kwargs = mock_managers["findings_manager"].add_finding.call_args[1]
            assert call_kwargs["severity"] == "critical"
            assert "Successful" in call_kwargs["title"]
            assert result["findings_added"] == 1
        finally:
            os.unlink(log_path)


class TestDisplayMethods:
    """Test display method existence and basic functionality."""

    def test_display_done_exists(self, handler):
        """display_done method should exist."""
        assert hasattr(handler, "display_done")

    def test_display_warning_exists(self, handler):
        """display_warning method should exist."""
        assert hasattr(handler, "display_warning")

    def test_display_error_exists(self, handler):
        """display_error method should exist."""
        assert hasattr(handler, "display_error")

    def test_display_no_results_exists(self, handler):
        """display_no_results method should exist."""
        assert hasattr(handler, "display_no_results")

    def test_display_warning_uses_parse_result_summary(self, handler, capsys):
        """display_warning should use summary from parse_result."""
        job = {"parse_result": {"summary": "Exploit failed: connection refused"}}
        handler.display_warning(job, "/fake/path")
        captured = capsys.readouterr()
        assert "connection refused" in captured.out.lower()

    def test_display_no_results_shows_reasons(self, handler, capsys):
        """display_no_results should show possible reasons."""
        job = {}
        handler.display_no_results(job, "/fake/path")
        captured = capsys.readouterr()
        assert "No session was created" in captured.out
        assert "Possible reasons" in captured.out


class TestRegistryIntegration:
    """Test that handler is discovered by registry."""

    def test_handler_discovered(self):
        """Handler should be discovered by registry."""
        from souleyez.handlers.registry import get_registry

        registry = get_registry()
        registry.reset()  # Clear any cached state

        # Force reimport to trigger discovery
        from souleyez.handlers import msf_exploit_handler  # noqa: F401

        handler = registry.get_handler("msf_exploit")
        assert handler is not None
        assert handler.tool_name == "msf_exploit"

    def test_registry_reports_capabilities(self):
        """Registry should report handler capabilities correctly."""
        from souleyez.handlers.registry import get_registry

        registry = get_registry()
        registry.reset()

        from souleyez.handlers import msf_exploit_handler  # noqa: F401

        assert registry.has_warning_handler("msf_exploit") is True
        assert registry.has_error_handler("msf_exploit") is True
        assert registry.has_no_results_handler("msf_exploit") is True
        assert registry.has_done_handler("msf_exploit") is True
