"""Review service - orchestrates business logic.

This module provides a high-level API for review operations,
decoupling business logic from CLI presentation.

Usage:
    config = load_config()
    registry = load_registry(config.registry_path)
    service = ReviewService(registry, config)

    # Get status
    state = service.get_registry_state()

    # Start a review
    result = service.start_review("src/module.py", assignee="@reviewer")

    # Sign off
    result = service.signoff("src/module.py", reviewer="@reviewer")
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING

from . import state_machine
from .errors import (
    FileChangedDuringReviewError,
    IssueNotClosedError,
    ModuleNotFoundError,
)
from .git import get_current_commit, has_changes_since
from .review_state import RegistryStateInfo, ReviewStateChecker

if TYPE_CHECKING:
    from .config import Config
    from .registry import ModuleStatus, Registry


@dataclass
class ReviewResult:
    """Result of a review operation."""

    success: bool
    message: str
    module: "ModuleStatus | None" = None
    issue_url: str | None = None
    commit: str | None = None


class ReviewService:
    """High-level API for review operations.

    This class:
    - Orchestrates business logic
    - Provides a clean interface for CLI or other consumers
    - Handles error cases consistently
    - Does NOT handle presentation (that's the CLI's job)
    """

    def __init__(self, registry: "Registry", config: "Config"):
        self.registry = registry
        self.config = config
        self.state_checker = ReviewStateChecker(config)

    def get_module(self, path: str) -> "ModuleStatus":
        """Get a module by path.

        Raises:
            ModuleNotFoundError: If module not in registry.
        """
        module = self.registry.get(path)
        if not module:
            raise ModuleNotFoundError(path)
        return module

    def get_registry_state(self) -> RegistryStateInfo:
        """Get categorized state of all modules."""
        return self.state_checker.get_registry_state(self.registry)

    def get_current_commit(self) -> str:
        """Get current git commit."""
        return get_current_commit(cwd=self.config.root)

    def check_file_changed_during_review(
        self, module: "ModuleStatus"
    ) -> tuple[bool, str | None, str | None]:
        """Check if file changed since review started.

        Returns:
            Tuple of (changed, start_commit, current_commit).
        """
        if not module.review_started_commit:
            return False, None, None

        current = self.get_current_commit()
        changed = has_changes_since(
            module.path,
            module.review_started_commit,
            cwd=self.config.root,
        )
        return changed, module.review_started_commit, current

    def start_review(
        self,
        path: str,
        assignee: str | None = None,
        issue_url: str | None = None,
        force: bool = False,
    ) -> ReviewResult:
        """Start a review for a module.

        Args:
            path: Module path.
            assignee: Reviewer to assign.
            issue_url: Pre-existing issue URL (if not creating new).
            force: Force start even if already reviewed.

        Returns:
            ReviewResult with success/failure info.
        """
        try:
            module = self.get_module(path)
        except ModuleNotFoundError as e:
            return ReviewResult(False, str(e))

        # Check if already reviewed and current
        if module.status == "reviewed" and not force:
            is_stale, _ = self.state_checker.is_module_stale(module)
            if not is_stale:
                return ReviewResult(
                    False,
                    f"{path} is already reviewed at current commit. "
                    "Use --force to start a new review.",
                    module=module,
                )

        current_commit = self.get_current_commit()

        try:
            state_machine.start_review(
                module,
                current_commit,
                issue_url=issue_url,
                assignee=assignee,
                force=force,
            )
        except Exception as e:
            return ReviewResult(False, str(e), module=module)

        return ReviewResult(
            True,
            f"Review started for {path}",
            module=module,
            issue_url=issue_url,
            commit=current_commit,
        )

    def signoff(
        self,
        path: str,
        reviewer: str,
        force: bool = False,
        acknowledge_changes: bool = False,
    ) -> ReviewResult:
        """Sign off on a completed review.

        Args:
            path: Module path.
            reviewer: Reviewer username(s).
            force: Force signoff regardless of state.
            acknowledge_changes: Acknowledge that file changed during review.

        Returns:
            ReviewResult with success/failure info.
        """
        try:
            module = self.get_module(path)
        except ModuleNotFoundError as e:
            return ReviewResult(False, str(e))

        # Check for changes during review
        if not acknowledge_changes and not force:
            changed, start_commit, current_commit = (
                self.check_file_changed_during_review(module)
            )
            if changed and start_commit and current_commit:
                raise FileChangedDuringReviewError(path, start_commit, current_commit)

        current_commit = self.get_current_commit()

        try:
            state_machine.signoff(
                module,
                current_commit,
                reviewer,
                force=force,
            )
        except Exception as e:
            return ReviewResult(False, str(e), module=module)

        return ReviewResult(
            True,
            f"Signed off on {path}",
            module=module,
            commit=current_commit,
        )

    def cancel_review(
        self,
        path: str,
        force: bool = False,
    ) -> ReviewResult:
        """Cancel a review.

        Args:
            path: Module path.
            force: Force cancel regardless of state.

        Returns:
            ReviewResult with the new status.
        """
        try:
            module = self.get_module(path)
        except ModuleNotFoundError as e:
            return ReviewResult(False, str(e))

        try:
            new_status = state_machine.cancel_review(module, force=force)
        except Exception as e:
            return ReviewResult(False, str(e), module=module)

        return ReviewResult(
            True,
            f"Review canceled for {path}. Status: {new_status}",
            module=module,
        )

    def restart_review(
        self,
        path: str,
    ) -> ReviewResult:
        """Restart a review at current commit.

        Args:
            path: Module path.

        Returns:
            ReviewResult with the new start commit.
        """
        try:
            module = self.get_module(path)
        except ModuleNotFoundError as e:
            return ReviewResult(False, str(e))

        current_commit = self.get_current_commit()

        try:
            state_machine.restart_review(module, current_commit)
        except Exception as e:
            return ReviewResult(False, str(e), module=module)

        return ReviewResult(
            True,
            f"Review restarted for {path} at {current_commit}",
            module=module,
            commit=current_commit,
        )

    def verify_issue_closed(
        self,
        module: "ModuleStatus",
        skip_verify: bool = False,
    ) -> tuple[bool, str | None, list[str]]:
        """Verify that a module's review issue is closed.

        Args:
            module: The module to check.
            skip_verify: If True, skip the check.

        Returns:
            Tuple of (is_closed, error_message, assignees).
            assignees is populated if issue was fetched successfully.
        """
        if skip_verify:
            return True, None, []

        issue_number = ReviewStateChecker.extract_issue_number(module.review_issue)
        if not issue_number:
            return True, None, []  # No issue to verify

        from .git import get_gitlab_host, get_gitlab_project
        from .gitlab import get_issue, gitlab_available

        host = get_gitlab_host(cwd=self.config.root)
        project = get_gitlab_project(cwd=self.config.root)

        if not gitlab_available(host):
            return True, "GitLab not available (no token)", []

        issue = get_issue(issue_number, project_path=project, host=host)
        if not issue:
            return True, f"Could not fetch issue #{issue_number}", []

        if issue.state != "closed":
            raise IssueNotClosedError(issue_number, issue.state)

        return True, None, issue.assignees

    def get_summary(self) -> dict:
        """Get review coverage summary.

        Returns dict with:
        - total, reviewed, in_progress, needs_review counts
        - total_lines, reviewed_lines
        - coverage_pct, line_coverage_pct
        - stale_count (reviewed but changed)
        """
        state = self.get_registry_state()

        total = state.total
        reviewed = state.reviewed_count
        in_progress = len(state.in_progress)
        needs_review = len(state.needs_review)
        stale = len(state.reviewed_stale)

        total_lines = state.total_lines
        reviewed_lines = state.reviewed_lines

        coverage_pct = (reviewed / total * 100) if total > 0 else 0
        line_coverage_pct = (
            (reviewed_lines / total_lines * 100) if total_lines > 0 else 0
        )

        return {
            "total": total,
            "reviewed": reviewed,
            "in_progress": in_progress,
            "needs_review": needs_review,
            "stale": stale,
            "total_lines": total_lines,
            "reviewed_lines": reviewed_lines,
            "coverage_pct": round(coverage_pct, 1),
            "line_coverage_pct": round(line_coverage_pct, 1),
        }
