"""Application context with dependency injection.

This module provides the canonical context creation pattern for dot-agent-kit,
following the architecture established in erk's core/context.py.

The DotAgentContext dataclass holds all dependencies (GitHub integrations, config)
and is created once at CLI entry point, then threaded through the application.
"""

import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING

import click
from erk_shared.extraction.claude_code_session_store import ClaudeCodeSessionStore
from erk_shared.git.abc import Git
from erk_shared.github.abc import GitHub
from erk_shared.github.issues import GitHubIssues, RealGitHubIssues
from erk_shared.prompt_executor import PromptExecutor

if TYPE_CHECKING:
    from erk_shared.github.types import RepoInfo


@dataclass(frozen=True)
class DotAgentContext:
    """Immutable context holding all dependencies for dot-agent-kit operations.

    Created at CLI entry point via create_context() and threaded through
    the application via Click's context system. Frozen to prevent accidental
    modification at runtime.

    Attributes:
        github_issues: GitHub Issues integration for querying/commenting
        git: Git operations integration for branch/commit queries
        github: GitHub integration for PR operations
        session_store: Session storage for Claude Code sessions
        prompt_executor: Executor for single-shot Claude prompts
        debug: Debug flag for error handling (full stack traces)
        repo_root: Repository root directory (detected at CLI entry)
        cwd: Current working directory (worktree path)
    """

    github_issues: GitHubIssues
    git: Git
    github: GitHub
    session_store: ClaudeCodeSessionStore
    prompt_executor: PromptExecutor
    debug: bool
    repo_root: Path
    cwd: Path

    @staticmethod
    def for_test(
        github_issues: GitHubIssues | None = None,
        git: Git | None = None,
        github: GitHub | None = None,
        session_store: ClaudeCodeSessionStore | None = None,
        prompt_executor: PromptExecutor | None = None,
        debug: bool = False,
        repo_root: Path | None = None,
        cwd: Path | None = None,
    ) -> "DotAgentContext":
        """Create test context with optional pre-configured implementations.

        Provides full control over all context parameters with sensible test defaults
        for any unspecified values. Uses fakes by default to avoid subprocess calls.

        Args:
            github_issues: Optional GitHubIssues implementation. If None, creates FakeGitHubIssues.
            git: Optional Git implementation. If None, creates FakeGit.
            github: Optional GitHub implementation. If None, creates FakeGitHub.
            session_store: Optional SessionStore. If None, creates FakeClaudeCodeSessionStore.
            prompt_executor: Optional PromptExecutor. If None, creates FakePromptExecutor.
            debug: Whether to enable debug mode (default False).
            repo_root: Repository root path (defaults to Path("/fake/repo"))
            cwd: Current working directory (defaults to Path("/fake/worktree"))

        Returns:
            DotAgentContext configured with provided values and test defaults

        Example:
            >>> from erk_shared.github.issues import FakeGitHubIssues
            >>> from erk_shared.git.fake import FakeGit
            >>> github = FakeGitHubIssues()
            >>> git_ops = FakeGit()
            >>> ctx = DotAgentContext.for_test(
            ...     github_issues=github, git=git_ops, debug=True
            ... )
        """
        from erk_shared.extraction.claude_code_session_store import FakeClaudeCodeSessionStore
        from erk_shared.git.fake import FakeGit
        from erk_shared.github.fake import FakeGitHub
        from erk_shared.github.issues import FakeGitHubIssues
        from erk_shared.prompt_executor.fake import FakePromptExecutor

        # Provide defaults - ensures non-None values for type checker
        resolved_github_issues: GitHubIssues = (
            github_issues if github_issues is not None else FakeGitHubIssues()
        )
        resolved_git: Git = git if git is not None else FakeGit()
        resolved_github: GitHub = github if github is not None else FakeGitHub()
        resolved_session_store: ClaudeCodeSessionStore = (
            session_store if session_store is not None else FakeClaudeCodeSessionStore()
        )
        resolved_prompt_executor: PromptExecutor = (
            prompt_executor if prompt_executor is not None else FakePromptExecutor()
        )
        resolved_repo_root: Path = repo_root if repo_root is not None else Path("/fake/repo")
        resolved_cwd: Path = cwd if cwd is not None else Path("/fake/worktree")

        return DotAgentContext(
            github_issues=resolved_github_issues,
            git=resolved_git,
            github=resolved_github,
            session_store=resolved_session_store,
            prompt_executor=resolved_prompt_executor,
            debug=debug,
            repo_root=resolved_repo_root,
            cwd=resolved_cwd,
        )


def get_repo_info(git: Git, repo_root: Path) -> "RepoInfo | None":
    """Detect repository info from git remote URL.

    Parses the origin remote URL to extract owner/name for GitHub API calls.
    Returns None if no origin remote is configured or URL cannot be parsed.

    Args:
        git: Git interface for operations
        repo_root: Repository root path

    Returns:
        RepoInfo with owner/name, or None if not determinable
    """
    from erk_shared.github.parsing import parse_git_remote_url
    from erk_shared.github.types import RepoInfo

    try:
        remote_url = git.get_remote_url(repo_root)
        owner, name = parse_git_remote_url(remote_url)
        return RepoInfo(owner=owner, name=name)
    except ValueError:
        # No origin remote configured or URL cannot be parsed
        return None


def create_context(*, debug: bool) -> DotAgentContext:
    """Create production context with real implementations.

    This is the canonical factory for creating the application context.
    Called once at CLI entry point to create the context for the entire
    command execution.

    Detects repository root using git rev-parse. Exits with error if not in a git repository.

    Args:
        debug: If True, enable debug mode (full stack traces in error handling)

    Returns:
        DotAgentContext with real GitHub integrations and detected repo_root

    Example:
        >>> ctx = create_context(debug=False)
        >>> issue_number = ctx.github_issues.create_issue(ctx.repo_root, title, body, labels)
    """
    from erk_shared.extraction.claude_code_session_store import RealClaudeCodeSessionStore
    from erk_shared.git.real import RealGit
    from erk_shared.github.real import RealGitHub
    from erk_shared.integrations.time.real import RealTime
    from erk_shared.prompt_executor.real import RealPromptExecutor

    # Detect repo root using git rev-parse
    result = subprocess.run(
        ["git", "rev-parse", "--show-toplevel"],
        capture_output=True,
        text=True,
        check=False,
    )

    if result.returncode != 0:
        click.echo("Error: Not in a git repository", err=True)
        raise SystemExit(1)

    repo_root = Path(result.stdout.strip())
    cwd = Path.cwd()

    # Create git instance and detect repo_info
    git = RealGit()
    repo_info = get_repo_info(git, repo_root)

    return DotAgentContext(
        github_issues=RealGitHubIssues(),
        git=git,
        github=RealGitHub(time=RealTime(), repo_info=repo_info),
        session_store=RealClaudeCodeSessionStore(),
        prompt_executor=RealPromptExecutor(),
        debug=debug,
        repo_root=repo_root,
        cwd=cwd,
    )
