"""Integration tests for review lifecycle through ReviewService.

Tests full state transitions with mocked git/gitlab, verifying that
the service layer correctly orchestrates state changes across
start -> signoff, start -> cancel, and start -> signoff -> start flows.
"""

from __future__ import annotations

from pathlib import Path
from unittest.mock import patch

import pytest

from glreview.config import Config
from glreview.registry import ModuleStatus, Registry
from glreview.review_service import ReviewService
from glreview.state_machine import Status


@pytest.fixture
def config(temp_dir):
    """Create a minimal Config."""
    return Config(
        root=temp_dir,
        sources=["src/**/*.py"],
    )


@pytest.fixture
def registry_with_module():
    """Create a registry with a single needs_review module."""
    registry = Registry()
    registry.set(
        ModuleStatus(
            path="src/main.py",
            status="needs_review",
            lines=100,
        )
    )
    return registry


def _make_service(registry, config):
    """Create a ReviewService with mocked git calls."""
    return ReviewService(registry, config)


class TestStartSignoffLifecycle:
    """Test start -> signoff lifecycle."""

    @patch("glreview.review_service.get_current_commit", return_value="commit_1")
    @patch("glreview.review_service.gitlab_available", return_value=False)
    def test_start_then_signoff(
        self, _mock_gitlab, _mock_commit, registry_with_module, config
    ):
        """Full start -> signoff lifecycle succeeds."""
        service = _make_service(registry_with_module, config)
        module = registry_with_module.get("src/main.py")

        # Start review (skip issue creation since no gitlab)
        result = service.start_review(
            "src/main.py", assignee="@alice", create_issue=False
        )
        assert result.success, result.message
        assert module.status == Status.IN_PROGRESS.value
        assert module.assigned_reviewer == "@alice"
        assert module.review_started_commit == "commit_1"

        # Sign off
        with patch(
            "glreview.review_service.has_changes_since", return_value=False
        ):
            result = service.signoff("src/main.py", reviewer="@alice")

        assert result.success, result.message
        assert module.status == Status.REVIEWED.value
        assert "@alice" in module.reviewers
        assert module.last_reviewed_commit == "commit_1"
        assert module.assigned_reviewer is None
        assert module.review_started_commit is None

    @patch("glreview.review_service.get_current_commit", return_value="commit_1")
    @patch("glreview.review_service.gitlab_available", return_value=False)
    def test_signoff_blocked_by_changes(
        self, _mock_gitlab, _mock_commit, registry_with_module, config
    ):
        """Signoff fails when file changed during review."""
        service = _make_service(registry_with_module, config)

        # Start review
        result = service.start_review(
            "src/main.py", create_issue=False
        )
        assert result.success

        # Try signoff with changes detected
        with patch(
            "glreview.review_service.has_changes_since", return_value=True
        ):
            result = service.signoff("src/main.py", reviewer="@alice")

        assert not result.success
        assert "changed during review" in result.message

        # Module should still be in progress
        module = registry_with_module.get("src/main.py")
        assert module.status == Status.IN_PROGRESS.value

    @patch("glreview.review_service.get_current_commit", return_value="commit_1")
    @patch("glreview.review_service.gitlab_available", return_value=False)
    def test_signoff_with_acknowledge(
        self, _mock_gitlab, _mock_commit, registry_with_module, config
    ):
        """Signoff succeeds with acknowledge_changes even when file changed."""
        service = _make_service(registry_with_module, config)

        result = service.start_review("src/main.py", create_issue=False)
        assert result.success

        with patch(
            "glreview.review_service.has_changes_since", return_value=True
        ):
            result = service.signoff(
                "src/main.py", reviewer="@alice", acknowledge_changes=True
            )

        assert result.success
        module = registry_with_module.get("src/main.py")
        assert module.status == Status.REVIEWED.value


class TestStartCancelLifecycle:
    """Test start -> cancel lifecycle."""

    @patch("glreview.review_service.get_current_commit", return_value="commit_1")
    @patch("glreview.review_service.gitlab_available", return_value=False)
    def test_start_then_cancel(
        self, _mock_gitlab, _mock_commit, registry_with_module, config
    ):
        """Start then cancel returns to needs_review."""
        service = _make_service(registry_with_module, config)

        result = service.start_review("src/main.py", create_issue=False)
        assert result.success

        result = service.cancel_review("src/main.py")
        assert result.success

        module = registry_with_module.get("src/main.py")
        assert module.status == Status.NEEDS_REVIEW.value
        assert module.review_issue is None
        assert module.assigned_reviewer is None
        assert module.review_started_commit is None

    @patch("glreview.review_service.get_current_commit", return_value="commit_1")
    @patch("glreview.review_service.gitlab_available", return_value=False)
    def test_cancel_needs_review_fails(
        self, _mock_gitlab, _mock_commit, registry_with_module, config
    ):
        """Cancel on needs_review module fails."""
        service = _make_service(registry_with_module, config)

        result = service.cancel_review("src/main.py")
        assert not result.success

    @patch("glreview.review_service.get_current_commit", return_value="commit_1")
    @patch("glreview.review_service.gitlab_available", return_value=False)
    def test_start_then_restart(
        self, _mock_gitlab, _mock_commit, registry_with_module, config
    ):
        """Start then restart updates the start commit."""
        service = _make_service(registry_with_module, config)

        result = service.start_review("src/main.py", create_issue=False)
        assert result.success

        module = registry_with_module.get("src/main.py")
        assert module.review_started_commit == "commit_1"

        # Advance commit
        _mock_commit.return_value = "commit_2"
        result = service.restart_review("src/main.py")
        assert result.success
        assert module.review_started_commit == "commit_2"
        assert module.status == Status.IN_PROGRESS.value


class TestReReviewLifecycle:
    """Test start -> signoff -> start (re-review) lifecycle."""

    @patch("glreview.review_service.get_current_commit", return_value="commit_1")
    @patch("glreview.review_service.gitlab_available", return_value=False)
    def test_start_signoff_then_re_review(
        self, _mock_gitlab, _mock_commit, registry_with_module, config
    ):
        """Complete lifecycle: start -> signoff -> start (re-review)."""
        service = _make_service(registry_with_module, config)
        module = registry_with_module.get("src/main.py")

        # First review cycle
        result = service.start_review("src/main.py", create_issue=False)
        assert result.success

        with patch(
            "glreview.review_service.has_changes_since", return_value=False
        ):
            result = service.signoff("src/main.py", reviewer="@alice")
        assert result.success
        assert module.status == Status.REVIEWED.value
        assert module.last_reviewed_commit == "commit_1"

        # Second review cycle (re-review)
        _mock_commit.return_value = "commit_2"
        result = service.start_review(
            "src/main.py", assignee="@bob", create_issue=False, force=True
        )
        assert result.success
        assert module.status == Status.IN_PROGRESS.value
        assert module.assigned_reviewer == "@bob"

        # Sign off second review
        with patch(
            "glreview.review_service.has_changes_since", return_value=False
        ):
            result = service.signoff("src/main.py", reviewer="@bob")
        assert result.success
        assert module.status == Status.REVIEWED.value
        assert module.last_reviewed_commit == "commit_2"
        # Both reviewers should be recorded
        assert "@alice" in module.reviewers
        assert "@bob" in module.reviewers

    @patch("glreview.review_service.get_current_commit", return_value="commit_1")
    @patch("glreview.review_service.gitlab_available", return_value=False)
    def test_cancel_reviewed_restores_state(
        self, _mock_gitlab, _mock_commit, registry_with_module, config
    ):
        """Cancel from re-review restores reviewed state."""
        service = _make_service(registry_with_module, config)
        module = registry_with_module.get("src/main.py")

        # Complete first review
        result = service.start_review("src/main.py", create_issue=False)
        assert result.success
        with patch(
            "glreview.review_service.has_changes_since", return_value=False
        ):
            result = service.signoff("src/main.py", reviewer="@alice")
        assert result.success

        # Start re-review
        _mock_commit.return_value = "commit_2"
        result = service.start_review(
            "src/main.py", create_issue=False, force=True
        )
        assert result.success
        assert module.status == Status.IN_PROGRESS.value

        # Cancel - should restore to reviewed (since it was previously reviewed)
        result = service.cancel_review("src/main.py")
        assert result.success
        assert module.status == Status.REVIEWED.value
        assert "@alice" in module.reviewers
        assert module.last_reviewed_commit == "commit_1"

    @patch("glreview.review_service.get_current_commit", return_value="commit_1")
    @patch("glreview.review_service.gitlab_available", return_value=False)
    def test_module_not_found(
        self, _mock_gitlab, _mock_commit, config
    ):
        """Operations on nonexistent module return failure."""
        registry = Registry()
        service = _make_service(registry, config)

        result = service.start_review("nonexistent.py", create_issue=False)
        assert not result.success
        assert "not found" in result.message.lower() or "not in registry" in result.message.lower()

        result = service.signoff("nonexistent.py", reviewer="@alice")
        assert not result.success

        result = service.cancel_review("nonexistent.py")
        assert not result.success
