# tests/test_steg.py
import shutil
import subprocess
from pathlib import Path
from unittest.mock import patch, MagicMock
import click
from click.testing import CliRunner
import pytest

from ctf.steg import crack_steghide, SteghideCrackResult

def test_crack_steghide_missing_binary():
    """
    Verifies that crack_steghide returns failure if steghide is not installed.
    """
    with patch("shutil.which", return_value=None):
        res = crack_steghide(Path("somefile.jpg"), ["pass1", "pass2"])
        assert not res.success
        assert "not found on system" in res.error_message

def test_crack_steghide_file_not_found():
    """
    Verifies that crack_steghide returns failure if the input file does not exist.
    """
    with patch("shutil.which", return_value="steghide"):
        res = crack_steghide(Path("non_existent_file_12345.jpg"), ["pass1", "pass2"])
        assert not res.success
        assert "File not found" in res.error_message

def test_crack_steghide_success():
    """
    Verifies that crack_steghide returns success, the password, and payload when cracked.
    """
    wordlist = ["wrong1", "correct_pass", "wrong2"]
    
    # We want to mock subprocess.run to write a payload to the out_file (-xf)
    # when the correct password is tried.
    def mock_run(cmd, *args, **kwargs):
        # cmd: ['steghide', 'extract', '-sf', '...', '-p', password, '-xf', out_file, '-f']
        password = cmd[5]
        out_file = cmd[7]
        mock_res = MagicMock()
        if password == "correct_pass":
            mock_res.returncode = 0
            # Write a mock payload file
            Path(out_file).write_bytes(b"flag{steghide_cracked_payload}")
        else:
            mock_res.returncode = 1
        return mock_res

    with patch("shutil.which", return_value="steghide"), \
         patch("subprocess.run", side_effect=mock_run), \
         patch("pathlib.Path.exists", return_value=True):
        
        res = crack_steghide(Path("mock_image.jpg"), wordlist)
        assert res.success
        assert res.password == "correct_pass"
        assert res.payload == b"flag{steghide_cracked_payload}"
        assert res.error_message is None

def test_crack_steghide_failure():
    """
    Verifies that crack_steghide returns failure if the wordlist is exhausted.
    """
    wordlist = ["wrong1", "wrong2"]
    
    def mock_run(cmd, *args, **kwargs):
        mock_res = MagicMock()
        mock_res.returncode = 1
        return mock_res

    with patch("shutil.which", return_value="steghide"), \
         patch("subprocess.run", side_effect=mock_run), \
         patch("pathlib.Path.exists", return_value=True):
        
        res = crack_steghide(Path("mock_image.jpg"), wordlist)
        assert not res.success
        assert res.password is None
        assert res.payload is None
        assert "Password not found in wordlist" in res.error_message

def test_cli_crack_steghide_success(tmp_path):
    """
    Verifies that the crack-steghide CLI command prints success panel and previews payload.
    """
    from ctf.main import cli
    runner = CliRunner()
    
    # Create temp wordlist file
    wordlist_file = tmp_path / "words.txt"
    wordlist_file.write_text("pass1\npass2\n")
    
    mock_file = tmp_path / "mock.jpg"
    mock_file.write_text("dummy")
    
    mock_result = SteghideCrackResult(
        success=True,
        password="pass2",
        payload=b"flag{cli_steg_preview}"
    )
    
    with patch("ctf.cli_steg.crack_steghide", return_value=mock_result):
        res = runner.invoke(cli, ["steg", "crack-steghide", str(mock_file), "-w", str(wordlist_file)])
        assert res.exit_code == 0
        assert "Successfully cracked!" in res.output
        assert "pass2" in res.output
        assert "flag{cli_steg_preview}" in res.output

def test_cli_crack_steghide_success_output_file(tmp_path):
    """
    Verifies that the crack-steghide CLI command writes the payload to a file when specified.
    """
    from ctf.main import cli
    runner = CliRunner()
    
    wordlist_file = tmp_path / "words.txt"
    wordlist_file.write_text("pass1\n")
    
    mock_file = tmp_path / "mock.jpg"
    mock_file.write_text("dummy")
    
    out_file = tmp_path / "out.txt"
    
    mock_result = SteghideCrackResult(
        success=True,
        password="pass1",
        payload=b"written_file_content"
    )
    
    with patch("ctf.cli_steg.crack_steghide", return_value=mock_result):
        res = runner.invoke(cli, ["steg", "crack-steghide", str(mock_file), "-w", str(wordlist_file), "-o", str(out_file)])
        assert res.exit_code == 0
        assert "Successfully cracked!" in res.output
        assert out_file.exists()
        assert out_file.read_bytes() == b"written_file_content"

def test_cli_crack_steghide_failure(tmp_path):
    """
    Verifies that the crack-steghide CLI command displays correct error message on failure.
    """
    from ctf.main import cli
    runner = CliRunner()
    
    wordlist_file = tmp_path / "words.txt"
    wordlist_file.write_text("pass1\n")
    
    mock_file = tmp_path / "mock.jpg"
    mock_file.write_text("dummy")
    
    mock_result = SteghideCrackResult(
        success=False,
        error_message="Password not found in wordlist."
    )
    
    with patch("ctf.cli_steg.crack_steghide", return_value=mock_result):
        res = runner.invoke(cli, ["steg", "crack-steghide", str(mock_file), "-w", str(wordlist_file)])
        assert res.exit_code == 0
        assert "Cracking failed: Password not found in wordlist" in res.output
