"""Repository-facing metabranch client."""

from __future__ import annotations

from ..exceptions import (
    BranchAlreadyExistsError,
    InvalidBranchError,
    InvalidMetabranchError,
)
from ..models import (
    BranchState,
    CheckoutResult,
    CONFIG_FILE_NAME,
    MetabranchDefinition,
    StatusResult,
)
from .context import _Context
from .workflows import WorkflowClient


class RepositoryClient:
    """Repository-facing metabranch operations."""

    def __init__(self, context: _Context) -> None:
        self._context = context

    def create(self, name: str, *branches: str, checkout: bool = True) -> None:
        self._context.ensure_no_active_workflow()
        if self.is_metabranch(name):
            raise BranchAlreadyExistsError()
        if self._context.git_client.branch_exists(name):
            raise InvalidMetabranchError(
                f"Branch '{name}' already exists as a regular Git branch. "
                "Converting an existing branch into a metabranch is forbidden. "
                "Create a new ephemeral metabranch with "
                "`metabranch checkout -b <name> ...` instead."
            )

        return_branch = self._context.git_client.current_branch_name()
        previous_active = self._context.local_state.get_active()
        with self._context.operation_lock("create", metabranch_name=name):
            base_ref = return_branch
            self._context.git_client.checkout(name, create=True)
            definition = self._context.with_validated_branch_updates(
                MetabranchDefinition(name=name, base_ref=base_ref),
                added=branches,
            )
            self._context.save_definition(definition, initialize=True)

        if branches:
            WorkflowClient(self._context).rebase(initialize=True)
        else:
            self._context.local_state.set_active(name)

        if checkout:
            return

        self._context.git_client.checkout(return_branch)
        self._context.local_state.set_active(previous_active)

    def is_metabranch(self, name: str) -> bool:
        """Return whether the named branch is a metabranch."""
        if not self._context.git_client.branch_exists(name):
            self._context.local_state.unregister(name)
            return False
        return self._context.tracked_config.exists_in_branch(name) or (
            name in self._context.local_state.list_names()
        )

    def is_plain_branch(self, name: str) -> bool:
        """Return whether the named branch exists but is not a metabranch."""
        return self._context.git_client.branch_exists(name) and not self.is_metabranch(
            name
        )

    def checkout(self, name: str) -> CheckoutResult:
        """Checkout a branch and update local metabranch state."""
        if not self._context.git_client.branch_exists(name):
            if name in self._context.local_state.list_names():
                self._context.local_state.unregister(name)
                raise InvalidBranchError(
                    f"Metabranch '{name}' is known locally but the Git branch is missing."
                )
            raise InvalidBranchError(f"Branch '{name}' does not exist.")
        is_metabranch = self.is_metabranch(name)
        if not is_metabranch:
            raise InvalidMetabranchError(
                f"Branch '{name}' exists, but it is a regular Git branch, not a "
                "metabranch. Converting an existing branch into or out of a "
                "metabranch is forbidden. Create a new ephemeral metabranch with "
                "`metabranch checkout -b <name> ...` instead."
            )
        self._context.git_client.checkout(name)
        definition = self._context.tracked_config.load_from_branch(name)
        if definition is None:
            definition = self._context.local_state.get_definition(name)
        if definition is not None:
            self._context.local_state.register(definition)
        self._context.local_state.set_active(name)
        return CheckoutResult(branch_name=name, is_metabranch=is_metabranch)

    def status(self) -> StatusResult:
        """Return structured status for the current repo state."""
        active_workflow = self._context.local_state.get_active_workflow()
        repo_lock = self._context.local_state.inspect_repo_lock()
        if self._context.tracked_config.exists():
            definition = self._context.tracked_config.load()
            tracking_branch = self._context.git_client.tracking_branch_name(
                definition.name
            )
            replayed_origin_lookup = getattr(
                self._context.git_client,
                "replayed_commit_origins",
                None,
            )
            replayed_origins = (
                replayed_origin_lookup(definition.name)
                if callable(replayed_origin_lookup)
                else set()
            )
            ahead_behind = self._context.git_client.ahead_behind(
                definition.name, tracking_branch
            )
            branch_states = tuple(
                self._branch_status(
                    definition,
                    branch_name,
                    replayed_origins=replayed_origins,
                )
                for branch_name in definition.branches
            )
            return StatusResult(
                branch_name=definition.name,
                is_metabranch=True,
                metabranch=definition,
                branch_states=branch_states,
                tracking_branch=tracking_branch,
                ahead=ahead_behind[0] if ahead_behind is not None else None,
                behind=ahead_behind[1] if ahead_behind is not None else None,
                remote_config_created_at=(
                    self._context.git_client.ref_commit_timestamp(
                        tracking_branch,
                        path=CONFIG_FILE_NAME,
                        first_addition=True,
                    )
                    if tracking_branch is not None
                    else None
                ),
                remote_config_updated_at=(
                    self._context.git_client.ref_commit_timestamp(
                        tracking_branch,
                        path=CONFIG_FILE_NAME,
                    )
                    if tracking_branch is not None
                    else None
                ),
                remote_head_updated_at=(
                    self._context.git_client.ref_commit_timestamp(tracking_branch)
                    if tracking_branch is not None
                    else None
                ),
                active_workflow=active_workflow,
                repo_lock=repo_lock,
                local_active_metabranch=self._context.local_state.get_active(),
            )

        current_branch = self._context.git_client.current_branch_name()
        return StatusResult(
            branch_name=current_branch,
            is_metabranch=False,
            active_workflow=active_workflow,
            repo_lock=repo_lock,
            local_active_metabranch=self._context.local_state.get_active(),
            notices=(f"Current branch {current_branch} is not a metabranch.",),
        )

    def list(self) -> list[MetabranchDefinition]:
        """Return metabranches discovered across local branches."""
        discovered: list[MetabranchDefinition] = []
        for branch_name in self._context.git_client.list_branches():
            definition = self._context.tracked_config.load_from_branch(branch_name)
            if definition is not None:
                discovered.append(definition)
        return discovered

    def active_workflow(self):
        """Return the active workflow journal, if any."""
        return self._context.local_state.get_active_workflow()

    def _branch_status(
        self,
        definition: MetabranchDefinition,
        branch_name: str,
        *,
        replayed_origins: set[str],
    ) -> BranchState:
        """Return status details for one contained branch."""
        if not self._context.git_client.branch_exists(branch_name):
            return BranchState(
                name=branch_name,
                role="contained",
                state="missing",
            )

        tracking_branch = self._context.git_client.tracking_branch_name(branch_name)
        ahead_behind = self._context.git_client.ahead_behind(
            branch_name, tracking_branch
        )
        branch_commits = self._context.git_client.list_unique_commits(
            branch_name,
            exclude_refs=(definition.base_ref,),
        )
        not_in_metabranch = sum(
            1
            for commit in branch_commits
            if not self._context.git_client.is_ancestor(commit.sha, definition.name)
            and commit.sha not in replayed_origins
        )
        return BranchState(
            name=branch_name,
            role="contained",
            state="available",
            in_metabranch=not_in_metabranch == 0,
            not_in_metabranch=not_in_metabranch,
            tracking_branch=tracking_branch,
            ahead=ahead_behind[0] if ahead_behind is not None else None,
            behind=ahead_behind[1] if ahead_behind is not None else None,
        )
