"""Tests for ANRS CLI upgrade command."""

import json
import pytest
from pathlib import Path
from click.testing import CliRunner

from anrs.main import cli
from anrs.upgrade import get_current_version, backup_state, upgrade_file
from anrs.backup import get_backup_dir


@pytest.fixture
def runner():
    """Create CLI runner."""
    return CliRunner()


@pytest.fixture
def anrs_repo(tmp_path):
    """Create a mock ANRS repository with .anrs directory."""
    anrs_dir = tmp_path / ".anrs"
    anrs_dir.mkdir()

    # Create config.json
    config = {
        "version": "0.0.1",
        "project": {"name": "test-project"}
    }
    (anrs_dir / "config.json").write_text(json.dumps(config, indent=2))

    # Create state.json
    state = {
        "status": "in_progress",
        "current_task": "task-123",
        "last_completed": "task-122"
    }
    (anrs_dir / "state.json").write_text(json.dumps(state, indent=2))

    # Create ENTRY.md
    (anrs_dir / "ENTRY.md").write_text("# Old Entry\n")

    return tmp_path


class TestGetCurrentVersion:
    """Tests for get_current_version function."""

    def test_get_version_exists(self, anrs_repo):
        """Test getting version when config exists."""
        anrs_dir = anrs_repo / ".anrs"
        version = get_current_version(anrs_dir)
        assert version == "0.0.1"

    def test_get_version_no_config(self, tmp_path):
        """Test getting version when no config."""
        anrs_dir = tmp_path / ".anrs"
        anrs_dir.mkdir()
        version = get_current_version(anrs_dir)
        assert version is None


class TestBackupState:
    """Tests for backup_state function."""

    def test_backup_creates_file(self, anrs_repo):
        """Test that backup creates a backup file."""
        anrs_dir = anrs_repo / ".anrs"
        backup_dir = get_backup_dir(anrs_dir)

        backup_state(anrs_dir, backup_dir)

        assert backup_dir.exists()
        # New backup format: state.json.YYYYMMDD_HHMMSS
        backup_files = list(backup_dir.glob("state.json.*"))
        assert len(backup_files) == 1

    def test_backup_no_state(self, tmp_path):
        """Test backup when no state file exists."""
        anrs_dir = tmp_path / ".anrs"
        anrs_dir.mkdir()
        backup_dir = get_backup_dir(anrs_dir)

        # Should not raise, returns None
        result = backup_state(anrs_dir, backup_dir)
        assert result is None


class TestUpgradeFile:
    """Tests for upgrade_file function."""

    def test_upgrade_overwrites(self, tmp_path):
        """Test that upgrade overwrites file."""
        source = tmp_path / "source.md"
        target = tmp_path / "target.md"

        source.write_text("new content")
        target.write_text("old content")

        result = upgrade_file(source, target, preserve_data=False)

        assert result is True
        assert target.read_text() == "new content"

    def test_upgrade_preserves_json_data(self, tmp_path):
        """Test that upgrade preserves user data in JSON."""
        source = tmp_path / "source.json"
        target = tmp_path / "target.json"

        source.write_text(json.dumps({
            "version": "0.2",
            "status": "idle",
            "current_task": None
        }))
        target.write_text(json.dumps({
            "version": "0.1",
            "status": "in_progress",
            "current_task": "task-123"
        }))

        result = upgrade_file(source, target, preserve_data=True)

        assert result is True
        data = json.loads(target.read_text())
        # User data preserved
        assert data["status"] == "in_progress"
        assert data["current_task"] == "task-123"


class TestUpgradeCommand:
    """Tests for anrs upgrade command."""

    def test_upgrade_not_anrs_repo(self, runner, tmp_path):
        """Test upgrade on non-ANRS repository."""
        result = runner.invoke(cli, ["upgrade", str(tmp_path)])
        assert result.exit_code != 0
        assert "Not an ANRS repository" in result.output

    def test_upgrade_dry_run(self, runner, anrs_repo):
        """Test upgrade --dry-run."""
        result = runner.invoke(cli, ["upgrade", "--dry-run", str(anrs_repo)])
        assert result.exit_code == 0
        assert "Dry run" in result.output

        # Should not modify files
        config = json.loads((anrs_repo / ".anrs" / "config.json").read_text())
        assert config["version"] == "0.0.1"

    def test_upgrade_creates_backup(self, runner, anrs_repo):
        """Test that upgrade creates backup."""
        result = runner.invoke(cli, ["upgrade", "--force", str(anrs_repo)])

        backup_dir = get_backup_dir(anrs_repo / ".anrs")
        # New backup format: state.json.YYYYMMDD_HHMMSS
        backup_files = list(backup_dir.glob("state.json.*"))
        assert len(backup_files) >= 1

    def test_upgrade_no_backup(self, runner, anrs_repo):
        """Test upgrade --no-backup."""
        result = runner.invoke(
            cli, ["upgrade", "--force", "--no-backup", str(anrs_repo)])

        backup_dir = anrs_repo / ".anrs" / ".backups"
        assert not backup_dir.exists()

    def test_upgrade_preserves_state(self, runner, anrs_repo):
        """Test that upgrade preserves state data."""
        result = runner.invoke(cli, ["upgrade", "--force", str(anrs_repo)])

        state = json.loads((anrs_repo / ".anrs" / "state.json").read_text())
        # User state preserved
        assert state["current_task"] == "task-123"
        assert state["status"] == "in_progress"

    def test_upgrade_list_backups(self, runner, anrs_repo):
        """Test --list-backups option."""
        # Create some backups first
        backup_dir = anrs_repo / ".anrs" / ".backups"
        backup_dir.mkdir()
        (backup_dir / "state.json.20240101_120000").write_text("{}")

        result = runner.invoke(
            cli, ["upgrade", "--list-backups", str(anrs_repo)])
        assert result.exit_code == 0
        assert "Backups" in result.output or "20240101" in result.output

    def test_upgrade_list_backups_no_anrs(self, runner, tmp_path):
        """Test --list-backups on non-ANRS repo."""
        result = runner.invoke(
            cli, ["upgrade", "--list-backups", str(tmp_path)])
        assert "No .anrs" in result.output or result.exit_code == 0

    def test_upgrade_file_nonexistent_source(self, tmp_path):
        """Test upgrade_file with non-existent source."""
        source = tmp_path / "nonexistent.md"
        target = tmp_path / "target.md"

        result = upgrade_file(source, target)

        assert result is False
        assert not target.exists()

    def test_upgrade_file_no_preserve(self, tmp_path):
        """Test upgrade_file without preserve (overwrite)."""
        source = tmp_path / "source.txt"
        target = tmp_path / "target.txt"

        source.write_text("new content")
        target.write_text("old content")

        result = upgrade_file(source, target, preserve_data=False)

        assert result is True
        assert target.read_text() == "new content"


class TestUpgradeEdgeCases:
    """Tests for edge cases in upgrade."""

    def test_get_version_json_error(self, tmp_path):
        """Test get_current_version with corrupted JSON."""
        anrs_dir = tmp_path / ".anrs"
        anrs_dir.mkdir()
        (anrs_dir / "config.json").write_text("{invalid json}")

        version = get_current_version(anrs_dir)
        assert version == "unknown"

    def test_get_version_io_error(self, tmp_path, monkeypatch):
        """Test get_current_version with IO error."""
        anrs_dir = tmp_path / ".anrs"
        anrs_dir.mkdir()
        config = anrs_dir / "config.json"
        config.write_text('{"version": "0.1"}')

        original_open = open

        def mock_open(path, *args, **kwargs):
            if "config.json" in str(path):
                raise IOError("Cannot read")
            return original_open(path, *args, **kwargs)

        monkeypatch.setattr("builtins.open", mock_open)

        version = get_current_version(anrs_dir)
        assert version == "unknown"

    def test_upgrade_versions_match(self, runner, anrs_repo):
        """Test upgrade when versions already match."""
        from anrs.constants import TEMPLATES_DIR

        # Get template version
        template_config = TEMPLATES_DIR / "files" / "config.json"
        if template_config.exists():
            with open(template_config) as f:
                template_version = json.load(f).get("version", "0.1")
        else:
            template_version = "0.1"

        # Set repo version to match template
        config_path = anrs_repo / ".anrs" / "config.json"
        config = json.loads(config_path.read_text())
        config["version"] = template_version
        config_path.write_text(json.dumps(config))

        result = runner.invoke(cli, ["upgrade", str(anrs_repo)])
        assert "up to date" in result.output.lower() or result.exit_code == 0

    def test_upgrade_file_json_merge_error(self, tmp_path):
        """Test upgrade_file with JSON merge error."""
        source = tmp_path / "source.json"
        target = tmp_path / "target.json"

        source.write_text('{"valid": true}')
        target.write_text("{invalid json}")

        # Should fall back to overwrite on merge error
        result = upgrade_file(source, target, preserve_data=True)
        # May succeed or fail depending on implementation
        assert isinstance(result, bool)

    def test_upgrade_file_io_error(self, tmp_path, monkeypatch):
        """Test upgrade_file with IO error."""
        import shutil

        source = tmp_path / "source.md"
        target = tmp_path / "target.md"
        source.write_text("content")

        def mock_copy(*args, **kwargs):
            raise IOError("Cannot copy")

        monkeypatch.setattr(shutil, "copy", mock_copy)

        result = upgrade_file(source, target, preserve_data=False)
        assert result is False

    def test_upgrade_dry_run_create_action(self, runner, tmp_path):
        """Test dry-run shows Create action for new files."""
        anrs_dir = tmp_path / ".anrs"
        anrs_dir.mkdir()
        # Only create config, no other files
        (anrs_dir / "config.json").write_text('{"version": "0.0.1"}')

        result = runner.invoke(cli, ["upgrade", "--dry-run", str(tmp_path)])
        assert "Create" in result.output or "Replace" in result.output or "Update" in result.output

    def test_upgrade_no_template_config(self, runner, anrs_repo, monkeypatch):
        """Test upgrade when template config doesn't exist."""
        from anrs import upgrade
        from pathlib import Path

        # Mock TEMPLATES_DIR to non-existent path
        fake_templates = Path("/nonexistent/templates")
        monkeypatch.setattr(upgrade, "TEMPLATES_DIR", fake_templates)

        # Should handle missing template gracefully
        result = runner.invoke(cli, ["upgrade", "--force", str(anrs_repo)])
        # May succeed with default version or fail gracefully
        assert result.exit_code in [0, 1]
