"""
Unit tests for the publish.py script module.
"""

import os
import sys
import pytest
from unittest.mock import patch, MagicMock
from pathlib import Path

from chroma_mcp.dev_scripts.publish import run_command, check_dist_files, main


def test_run_command(tmp_path, monkeypatch):
    """Test the run_command helper."""
    calls = []

    def fake_run(cmd, cwd=None):
        calls.append((cmd, cwd))

        class R:
            returncode = 321

        return R()

    monkeypatch.setattr("subprocess.run", fake_run)
    result = run_command(["ls"], cwd=Path("/tmp"))
    assert result == 321
    assert calls == [(["ls"], Path("/tmp"))]


def test_check_dist_files_no_files(tmp_path):
    """Check that check_dist_files returns False when no dist files."""
    assert not check_dist_files(tmp_path)


def test_check_dist_files_with_files(tmp_path):
    """Check that check_dist_files returns True when both wheel and tar.gz exist."""
    project = tmp_path
    dist_dir = project / "dist"
    dist_dir.mkdir()
    (dist_dir / "file.whl").write_text("")
    (dist_dir / "file.tar.gz").write_text("")
    assert check_dist_files(project)


@patch("sys.argv", ["publish"])
@patch("chroma_mcp.dev_scripts.publish.check_dist_files", return_value=True)
@patch("subprocess.run")
@patch("chroma_mcp.dev_scripts.publish.run_command")
def test_main_full_flow(mock_run_command, mock_subprocess_run, mock_check_dist):
    """Test publish main full flow without skip flags."""
    # Mock run_command and subprocess.run to succeed
    # run_command calls: pip install, tests, build, upload
    mock_run_command.side_effect = [0, 0, 0, 0]
    mock_subprocess_run.return_value = MagicMock(returncode=0)

    ret = main()
    assert ret == 0
    # Check that pip install, test, build, and upload are invoked
    tokens = [token for call, _ in mock_run_command.call_args_list for token in call[0]]
    assert "pip" in tokens
    assert "test" in tokens
    assert "build" in tokens
    assert "twine" in tokens


@patch("sys.argv", ["publish", "--skip-build", "--skip-tests"])
@patch("chroma_mcp.dev_scripts.publish.check_dist_files", return_value=True)
@patch("subprocess.run")
@patch("chroma_mcp.dev_scripts.publish.run_command")
def test_main_skip_flags(mock_run_command, mock_subprocess_run, mock_check_dist):
    """Test publish main with skip-build and skip-tests flags."""
    # run_command calls: pip install deps and upload
    mock_run_command.side_effect = [0, 0]
    mock_subprocess_run.return_value = MagicMock(returncode=0)

    ret = main()
    assert ret == 0
    calls = mock_run_command.call_args_list
    # Expect two calls: dependencies install and upload
    assert len(calls) == 2
    # First call installs dependencies
    assert sys.executable in calls[0][0][0]
    assert "pip" in calls[0][0][0]
    # Second call uploads via twine
    assert "twine" in calls[1][0][0]


@patch("sys.argv", ["publish"])
@patch("chroma_mcp.dev_scripts.publish.check_dist_files", return_value=True)
@patch("subprocess.run")
@patch("chroma_mcp.dev_scripts.publish.run_command")
def test_main_hatch_installation_flow(mock_run_command, mock_subprocess_run, mock_check_dist):
    """Test publish main when hatch is not installed, triggering installation of hatch."""
    # run_command calls: deps, pip install hatch, test, build, upload
    mock_run_command.side_effect = [0, 0, 0, 0, 0]

    def fake_run(cmd, cwd=None, check=False, capture_output=False):
        # Simulate FileNotFoundError for hatch version check
        raise FileNotFoundError()

    mock_subprocess_run.side_effect = fake_run

    ret = main()
    assert ret == 0
    calls = mock_run_command.call_args_list
    # Ensure pip install dependencies is first
    assert "pip" in calls[0][0][0]
    # Ensure pip install hatch is called
    tokens_install_hatch = calls[1][0][0]
    assert "install" in tokens_install_hatch
    assert "hatch" in tokens_install_hatch
    # Ensure test and build commands are called
    assert any("test" in token for token in calls[2][0][0])
    assert any("build" in token for token in calls[3][0][0])
    # Ensure upload via twine is last
    assert any("twine" in token for token in calls[4][0][0])


@patch("sys.argv", ["publish"])
@patch("chroma_mcp.dev_scripts.publish.check_dist_files", return_value=True)
@patch("subprocess.run")
@patch("chroma_mcp.dev_scripts.publish.run_command")
def test_publish_dep_install_fail(mock_run_command, mock_subprocess_run, mock_check_dist):
    """Test publish main returns error when dependency installation fails."""
    # run_command pip install dependencies fails
    mock_run_command.return_value = 1
    mock_subprocess_run.return_value = MagicMock(returncode=0)
    ret = main()
    assert ret == 1


@patch("sys.argv", ["publish"])
@patch("chroma_mcp.dev_scripts.publish.check_dist_files", return_value=True)
@patch("subprocess.run")
@patch("chroma_mcp.dev_scripts.publish.run_command")
def test_publish_tests_fail(mock_run_command, mock_subprocess_run, mock_check_dist):
    """Test publish main returns error when tests fail."""
    # pip install deps succeeds
    # run tests fails
    # Provide side_effect: [dep_install, tests_fail]
    mock_run_command.side_effect = [0, 1]
    mock_subprocess_run.return_value = MagicMock(returncode=0)
    ret = main()
    assert ret == 1


@patch("sys.argv", ["publish"])
@patch("chroma_mcp.dev_scripts.publish.check_dist_files", return_value=True)
@patch("subprocess.run")
@patch("chroma_mcp.dev_scripts.publish.run_command")
def test_publish_build_fail(mock_run_command, mock_subprocess_run, mock_check_dist):
    """Test publish main returns error when build fails."""
    # side_effect: [dep_install, test_success, build_fail]
    mock_run_command.side_effect = [0, 0, 1]
    mock_subprocess_run.return_value = MagicMock(returncode=0)
    ret = main()
    assert ret == 1


@patch("sys.argv", ["publish"])
@patch("chroma_mcp.dev_scripts.publish.check_dist_files", return_value=False)
@patch("subprocess.run")
@patch("chroma_mcp.dev_scripts.publish.run_command")
def test_publish_check_dist_fail(mock_run_command, mock_subprocess_run, mock_check_dist):
    """Test publish main returns error when distribution files are missing."""
    mock_run_command.side_effect = [0, 0, 0]
    mock_subprocess_run.return_value = MagicMock(returncode=0)
    ret = main()
    assert ret == 1


@patch("sys.argv", ["publish"])
@patch("chroma_mcp.dev_scripts.publish.check_dist_files", return_value=True)
@patch("subprocess.run")
@patch("chroma_mcp.dev_scripts.publish.run_command")
def test_publish_upload_fail(mock_run_command, mock_subprocess_run, mock_check_dist):
    """Test publish main returns error when upload fails."""
    # side_effect: [dep_install, test_success, build_success, upload_fail]
    mock_run_command.side_effect = [0, 0, 0, 1]
    mock_subprocess_run.return_value = MagicMock(returncode=0)
    ret = main()
    assert ret == 1


@patch("sys.argv", ["publish"])
@patch("chroma_mcp.dev_scripts.publish.check_dist_files", return_value=True)
@patch("subprocess.run")
@patch("chroma_mcp.dev_scripts.publish.run_command")
def test_publish_hatch_install_fail(mock_run_command, mock_subprocess_run, mock_check_dist):
    """Test publish main aborts when hatch installation fails."""
    # pip install deps succeeds, hatch not installed -> pip install hatch fails
    mock_run_command.side_effect = [0, 1]

    def fake_run(cmd, cwd=None, check=False, capture_output=False):
        raise FileNotFoundError()

    mock_subprocess_run.side_effect = fake_run
    ret = main()
    assert ret == 1


@patch("sys.argv", ["publish", "--version", "2.0.0"])
@patch("chroma_mcp.dev_scripts.publish.check_dist_files", return_value=True)
@patch("subprocess.run")
@patch("chroma_mcp.dev_scripts.publish.run_command")
def test_main_version_flag(mock_run_command, mock_subprocess_run, mock_check_dist):
    """Test publish main accepts --version flag and runs full flow."""
    mock_run_command.side_effect = [0, 0, 0, 0]
    mock_subprocess_run.return_value = MagicMock(returncode=0)
    ret = main()
    assert ret == 0
    # Ensure full publish flow (install deps, tests, build, upload)
    assert mock_run_command.call_count == 4


@patch("sys.argv", ["publish", "--yes", "--skip-tests", "--skip-build"])
@patch("chroma_mcp.dev_scripts.publish.check_dist_files", return_value=True)
@patch("subprocess.run")
@patch("chroma_mcp.dev_scripts.publish.run_command")
def test_main_yes_flag(mock_run_command, mock_subprocess_run, mock_check_dist):
    """Test publish main accepts --yes flag and skip flags."""
    mock_run_command.side_effect = [0, 0]
    mock_subprocess_run.return_value = MagicMock(returncode=0)
    ret = main()
    assert ret == 0
    # Should only install dependencies and upload
    calls = mock_run_command.call_args_list
    assert len(calls) == 2
    # First call: pip install dependencies
    assert any("pip" in token for token in calls[0][0][0])
    # Second call: upload via twine
    assert any("twine" in token for token in calls[1][0][0])


@patch("sys.argv", ["publish"])
@patch("chroma_mcp.dev_scripts.publish.check_dist_files", return_value=True)
@patch("subprocess.run")
@patch("chroma_mcp.dev_scripts.publish.run_command")
def test_main_interactive_token_prompt(mock_run_command, mock_subprocess_run, mock_check_dist, tmp_path, monkeypatch):
    """Test publish main prompts for token when .pypirc is missing."""
    # Simulate no .pypirc in home directory
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    # Ensure no .pypirc file
    assert not (tmp_path / ".pypirc").exists()
    # Mock getpass for interactive token input
    monkeypatch.setattr("chroma_mcp.dev_scripts.publish.getpass.getpass", lambda prompt: "TOKEN123")
    # Simulate successful run_command calls: deps, tests, build, upload
    mock_run_command.side_effect = [0, 0, 0, 0]
    mock_subprocess_run.return_value = MagicMock(returncode=0)
    ret = main()
    assert ret == 0
    # Verify upload call includes provided token credentials
    upload_call = mock_run_command.call_args_list[-1][0][0]
    assert "-u" in upload_call and "TOKEN123" in upload_call and "-p" in upload_call


@patch("sys.argv", ["publish"])
@patch("chroma_mcp.dev_scripts.publish.check_dist_files", return_value=True)
@patch("subprocess.run")
@patch("chroma_mcp.dev_scripts.publish.run_command")
def test_main_use_pypirc_config(mock_run_command, mock_subprocess_run, mock_check_dist, tmp_path, monkeypatch):
    """Test publish main uses .pypirc config when available."""
    # Simulate .pypirc present in home directory
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    (tmp_path / ".pypirc").write_text("[distutils]")
    # Simulate successful run_command calls: deps, tests, build, upload
    mock_run_command.side_effect = [0, 0, 0, 0]
    mock_subprocess_run.return_value = MagicMock(returncode=0)
    ret = main()
    assert ret == 0
    # Verify upload call uses repository alias (-r)
    upload_call = mock_run_command.call_args_list[-1][0][0]
    assert "-r" in upload_call and "pypi" in upload_call
