"""
Data models for Git Operations MCP Server.

Defines data structures for git operations, workspace management,
and provider interactions.
"""

from dataclasses import dataclass, field
from datetime import UTC, datetime
from enum import Enum
from typing import Any

from pydantic import BaseModel, Field


class FileChangeType(str, Enum):
    """Types of file changes in git."""

    ADDED = "added"
    MODIFIED = "modified"
    DELETED = "deleted"
    RENAMED = "renamed"
    COPIED = "copied"
    UNTRACKED = "untracked"
    IGNORED = "ignored"


class MergeStrategy(str, Enum):
    """Merge strategies for pull requests."""

    MERGE = "merge"  # Create a merge commit
    SQUASH = "squash"  # Squash and merge
    REBASE = "rebase"  # Rebase and merge


class PRState(str, Enum):
    """Pull request states."""

    OPEN = "open"
    CLOSED = "closed"
    MERGED = "merged"


class ProviderType(str, Enum):
    """Supported git providers."""

    GITEA = "gitea"
    GITHUB = "github"
    GITLAB = "gitlab"


class WorkspaceState(str, Enum):
    """Workspace lifecycle states."""

    INITIALIZING = "initializing"
    READY = "ready"
    LOCKED = "locked"
    STALE = "stale"
    DELETED = "deleted"


# Dataclasses for internal data structures


@dataclass
class FileChange:
    """A file change in git status."""

    path: str
    change_type: FileChangeType
    old_path: str | None = None  # For renames
    additions: int = 0
    deletions: int = 0

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "path": self.path,
            "change_type": self.change_type.value,
            "old_path": self.old_path,
            "additions": self.additions,
            "deletions": self.deletions,
        }


@dataclass
class BranchInfo:
    """Information about a git branch."""

    name: str
    is_current: bool = False
    is_remote: bool = False
    tracking_branch: str | None = None
    commit_sha: str | None = None
    commit_message: str | None = None
    ahead: int = 0
    behind: int = 0

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "name": self.name,
            "is_current": self.is_current,
            "is_remote": self.is_remote,
            "tracking_branch": self.tracking_branch,
            "commit_sha": self.commit_sha,
            "commit_message": self.commit_message,
            "ahead": self.ahead,
            "behind": self.behind,
        }


@dataclass
class CommitInfo:
    """Information about a git commit."""

    sha: str
    short_sha: str
    message: str
    author_name: str
    author_email: str
    authored_date: datetime
    committer_name: str
    committer_email: str
    committed_date: datetime
    parents: list[str] = field(default_factory=list)

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "sha": self.sha,
            "short_sha": self.short_sha,
            "message": self.message,
            "author_name": self.author_name,
            "author_email": self.author_email,
            "authored_date": self.authored_date.isoformat(),
            "committer_name": self.committer_name,
            "committer_email": self.committer_email,
            "committed_date": self.committed_date.isoformat(),
            "parents": self.parents,
        }


@dataclass
class DiffHunk:
    """A hunk of diff content."""

    old_start: int
    old_lines: int
    new_start: int
    new_lines: int
    content: str

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "old_start": self.old_start,
            "old_lines": self.old_lines,
            "new_start": self.new_start,
            "new_lines": self.new_lines,
            "content": self.content,
        }


@dataclass
class FileDiff:
    """Diff for a single file."""

    path: str
    change_type: FileChangeType
    old_path: str | None = None
    hunks: list[DiffHunk] = field(default_factory=list)
    additions: int = 0
    deletions: int = 0
    is_binary: bool = False

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "path": self.path,
            "change_type": self.change_type.value,
            "old_path": self.old_path,
            "hunks": [h.to_dict() for h in self.hunks],
            "additions": self.additions,
            "deletions": self.deletions,
            "is_binary": self.is_binary,
        }


@dataclass
class PRInfo:
    """Information about a pull request."""

    number: int
    title: str
    body: str
    state: PRState
    source_branch: str
    target_branch: str
    author: str
    created_at: datetime
    updated_at: datetime
    merged_at: datetime | None = None
    closed_at: datetime | None = None
    url: str | None = None
    labels: list[str] = field(default_factory=list)
    assignees: list[str] = field(default_factory=list)
    reviewers: list[str] = field(default_factory=list)
    mergeable: bool | None = None
    draft: bool = False

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "number": self.number,
            "title": self.title,
            "body": self.body,
            "state": self.state.value,
            "source_branch": self.source_branch,
            "target_branch": self.target_branch,
            "author": self.author,
            "created_at": self.created_at.isoformat(),
            "updated_at": self.updated_at.isoformat(),
            "merged_at": self.merged_at.isoformat() if self.merged_at else None,
            "closed_at": self.closed_at.isoformat() if self.closed_at else None,
            "url": self.url,
            "labels": self.labels,
            "assignees": self.assignees,
            "reviewers": self.reviewers,
            "mergeable": self.mergeable,
            "draft": self.draft,
        }


@dataclass
class WorkspaceInfo:
    """Information about a project workspace."""

    project_id: str
    path: str
    state: WorkspaceState
    repo_url: str | None = None
    current_branch: str | None = None
    last_accessed: datetime = field(default_factory=lambda: datetime.now(UTC))
    size_bytes: int = 0
    lock_holder: str | None = None
    lock_expires: datetime | None = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "project_id": self.project_id,
            "path": self.path,
            "state": self.state.value,
            "repo_url": self.repo_url,
            "current_branch": self.current_branch,
            "last_accessed": self.last_accessed.isoformat(),
            "size_bytes": self.size_bytes,
            "lock_holder": self.lock_holder,
            "lock_expires": self.lock_expires.isoformat()
            if self.lock_expires
            else None,
        }


# Pydantic Request/Response Models


class CloneRequest(BaseModel):
    """Request to clone a repository."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    repo_url: str = Field(..., description="Repository URL to clone")
    branch: str | None = Field(
        default=None, description="Branch to checkout after clone"
    )
    depth: int | None = Field(
        default=None, ge=1, description="Shallow clone depth (None = full clone)"
    )


class CloneResult(BaseModel):
    """Result of a clone operation."""

    success: bool = Field(..., description="Whether clone succeeded")
    project_id: str = Field(..., description="Project ID")
    workspace_path: str = Field(..., description="Path to cloned workspace")
    branch: str = Field(..., description="Current branch after clone")
    commit_sha: str = Field(..., description="HEAD commit SHA")
    error: str | None = Field(default=None, description="Error message if failed")


class StatusRequest(BaseModel):
    """Request for git status."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    include_untracked: bool = Field(default=True, description="Include untracked files")


class StatusResult(BaseModel):
    """Result of a status operation."""

    project_id: str = Field(..., description="Project ID")
    branch: str = Field(..., description="Current branch")
    commit_sha: str = Field(..., description="HEAD commit SHA")
    is_clean: bool = Field(..., description="Whether working tree is clean")
    staged: list[dict[str, Any]] = Field(
        default_factory=list, description="Staged changes"
    )
    unstaged: list[dict[str, Any]] = Field(
        default_factory=list, description="Unstaged changes"
    )
    untracked: list[str] = Field(default_factory=list, description="Untracked files")
    ahead: int = Field(default=0, description="Commits ahead of upstream")
    behind: int = Field(default=0, description="Commits behind upstream")


class BranchRequest(BaseModel):
    """Request for branch operations."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    branch_name: str = Field(..., description="Branch name")
    from_ref: str | None = Field(
        default=None, description="Reference to create branch from"
    )
    checkout: bool = Field(default=True, description="Checkout after creation")


class BranchResult(BaseModel):
    """Result of a branch operation."""

    success: bool = Field(..., description="Whether operation succeeded")
    branch: str = Field(..., description="Branch name")
    commit_sha: str | None = Field(default=None, description="HEAD commit SHA")
    is_current: bool = Field(default=False, description="Whether branch is checked out")
    error: str | None = Field(default=None, description="Error message if failed")


class ListBranchesRequest(BaseModel):
    """Request to list branches."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    include_remote: bool = Field(default=False, description="Include remote branches")


class ListBranchesResult(BaseModel):
    """Result of listing branches."""

    project_id: str = Field(..., description="Project ID")
    current_branch: str = Field(..., description="Currently checked out branch")
    local_branches: list[dict[str, Any]] = Field(
        default_factory=list, description="Local branches"
    )
    remote_branches: list[dict[str, Any]] = Field(
        default_factory=list, description="Remote branches"
    )


class CheckoutRequest(BaseModel):
    """Request to checkout a branch or ref."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    ref: str = Field(..., description="Branch, tag, or commit to checkout")
    create_branch: bool = Field(default=False, description="Create new branch")
    force: bool = Field(default=False, description="Force checkout (discard changes)")


class CheckoutResult(BaseModel):
    """Result of a checkout operation."""

    success: bool = Field(..., description="Whether checkout succeeded")
    ref: str = Field(..., description="Checked out reference")
    commit_sha: str | None = Field(default=None, description="HEAD commit SHA")
    error: str | None = Field(default=None, description="Error message if failed")


class CommitRequest(BaseModel):
    """Request to create a commit."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    message: str = Field(..., description="Commit message")
    files: list[str] | None = Field(
        default=None, description="Files to commit (None = all staged)"
    )
    author_name: str | None = Field(default=None, description="Author name override")
    author_email: str | None = Field(default=None, description="Author email override")
    allow_empty: bool = Field(default=False, description="Allow empty commit")


class CommitResult(BaseModel):
    """Result of a commit operation."""

    success: bool = Field(..., description="Whether commit succeeded")
    commit_sha: str | None = Field(default=None, description="New commit SHA")
    short_sha: str | None = Field(default=None, description="Short commit SHA")
    message: str | None = Field(default=None, description="Commit message")
    files_changed: int = Field(default=0, description="Number of files changed")
    insertions: int = Field(default=0, description="Lines added")
    deletions: int = Field(default=0, description="Lines removed")
    error: str | None = Field(default=None, description="Error message if failed")


class PushRequest(BaseModel):
    """Request to push to remote."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    branch: str | None = Field(
        default=None, description="Branch to push (None = current)"
    )
    remote: str = Field(default="origin", description="Remote name")
    force: bool = Field(default=False, description="Force push")
    set_upstream: bool = Field(default=True, description="Set upstream tracking")


class PushResult(BaseModel):
    """Result of a push operation."""

    success: bool = Field(..., description="Whether push succeeded")
    branch: str = Field(..., description="Pushed branch")
    remote: str = Field(..., description="Remote name")
    commits_pushed: int = Field(default=0, description="Number of commits pushed")
    error: str | None = Field(default=None, description="Error message if failed")


class PullRequest(BaseModel):
    """Request to pull from remote."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    branch: str | None = Field(
        default=None, description="Branch to pull (None = current)"
    )
    remote: str = Field(default="origin", description="Remote name")
    rebase: bool = Field(default=False, description="Rebase instead of merge")


class PullResult(BaseModel):
    """Result of a pull operation."""

    success: bool = Field(..., description="Whether pull succeeded")
    branch: str = Field(..., description="Pulled branch")
    commits_received: int = Field(default=0, description="New commits received")
    fast_forward: bool = Field(default=False, description="Was fast-forward")
    conflicts: list[str] = Field(
        default_factory=list, description="Conflicting files if any"
    )
    error: str | None = Field(default=None, description="Error message if failed")


class DiffRequest(BaseModel):
    """Request for diff."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    base: str | None = Field(
        default=None, description="Base reference (None = working tree)"
    )
    head: str | None = Field(default=None, description="Head reference (None = HEAD)")
    files: list[str] | None = Field(default=None, description="Specific files to diff")
    context_lines: int = Field(default=3, ge=0, description="Context lines")


class DiffResult(BaseModel):
    """Result of a diff operation."""

    project_id: str = Field(..., description="Project ID")
    base: str | None = Field(default=None, description="Base reference")
    head: str | None = Field(default=None, description="Head reference")
    files: list[dict[str, Any]] = Field(default_factory=list, description="File diffs")
    total_additions: int = Field(default=0, description="Total lines added")
    total_deletions: int = Field(default=0, description="Total lines removed")
    files_changed: int = Field(default=0, description="Number of files changed")


class LogRequest(BaseModel):
    """Request for commit log."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    ref: str | None = Field(default=None, description="Reference to start from")
    limit: int = Field(default=20, ge=1, le=100, description="Max commits to return")
    skip: int = Field(default=0, ge=0, description="Commits to skip")
    path: str | None = Field(default=None, description="Filter by path")


class LogResult(BaseModel):
    """Result of a log operation."""

    project_id: str = Field(..., description="Project ID")
    commits: list[dict[str, Any]] = Field(
        default_factory=list, description="Commit history"
    )
    total_commits: int = Field(default=0, description="Total commits in range")


# PR Operations


class CreatePRRequest(BaseModel):
    """Request to create a pull request."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    title: str = Field(..., description="PR title")
    body: str = Field(default="", description="PR description")
    source_branch: str = Field(..., description="Source branch")
    target_branch: str = Field(default="main", description="Target branch")
    draft: bool = Field(default=False, description="Create as draft")
    labels: list[str] = Field(default_factory=list, description="Labels to add")
    assignees: list[str] = Field(default_factory=list, description="Assignees")
    reviewers: list[str] = Field(default_factory=list, description="Reviewers")


class CreatePRResult(BaseModel):
    """Result of creating a pull request."""

    success: bool = Field(..., description="Whether creation succeeded")
    pr_number: int | None = Field(default=None, description="PR number")
    pr_url: str | None = Field(default=None, description="PR URL")
    error: str | None = Field(default=None, description="Error message if failed")


class GetPRRequest(BaseModel):
    """Request to get a pull request."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    pr_number: int = Field(..., description="PR number")


class GetPRResult(BaseModel):
    """Result of getting a pull request."""

    success: bool = Field(..., description="Whether fetch succeeded")
    pr: dict[str, Any] | None = Field(default=None, description="PR info")
    error: str | None = Field(default=None, description="Error message if failed")


class ListPRsRequest(BaseModel):
    """Request to list pull requests."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    state: PRState | None = Field(default=None, description="Filter by state")
    author: str | None = Field(default=None, description="Filter by author")
    limit: int = Field(default=20, ge=1, le=100, description="Max PRs to return")


class ListPRsResult(BaseModel):
    """Result of listing pull requests."""

    success: bool = Field(..., description="Whether list succeeded")
    pull_requests: list[dict[str, Any]] = Field(default_factory=list, description="PRs")
    total_count: int = Field(default=0, description="Total matching PRs")
    error: str | None = Field(default=None, description="Error message if failed")


class MergePRRequest(BaseModel):
    """Request to merge a pull request."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    pr_number: int = Field(..., description="PR number")
    merge_strategy: MergeStrategy = Field(
        default=MergeStrategy.MERGE, description="Merge strategy"
    )
    commit_message: str | None = Field(
        default=None, description="Custom merge commit message"
    )
    delete_branch: bool = Field(
        default=True, description="Delete source branch after merge"
    )


class MergePRResult(BaseModel):
    """Result of merging a pull request."""

    success: bool = Field(..., description="Whether merge succeeded")
    merge_commit_sha: str | None = Field(default=None, description="Merge commit SHA")
    branch_deleted: bool = Field(
        default=False, description="Whether branch was deleted"
    )
    error: str | None = Field(default=None, description="Error message if failed")


class UpdatePRRequest(BaseModel):
    """Request to update a pull request."""

    project_id: str = Field(..., description="Project ID for scoping")
    agent_id: str = Field(..., description="Agent ID making the request")
    pr_number: int = Field(..., description="PR number")
    title: str | None = Field(default=None, description="New title")
    body: str | None = Field(default=None, description="New description")
    state: PRState | None = Field(default=None, description="New state")
    labels: list[str] | None = Field(default=None, description="Replace labels")
    assignees: list[str] | None = Field(default=None, description="Replace assignees")


class UpdatePRResult(BaseModel):
    """Result of updating a pull request."""

    success: bool = Field(..., description="Whether update succeeded")
    pr: dict[str, Any] | None = Field(default=None, description="Updated PR info")
    error: str | None = Field(default=None, description="Error message if failed")


# Workspace Operations


class GetWorkspaceRequest(BaseModel):
    """Request to get or create workspace."""

    project_id: str = Field(..., description="Project ID")
    agent_id: str = Field(..., description="Agent ID making the request")


class GetWorkspaceResult(BaseModel):
    """Result of getting workspace."""

    success: bool = Field(..., description="Whether operation succeeded")
    workspace: dict[str, Any] | None = Field(default=None, description="Workspace info")
    error: str | None = Field(default=None, description="Error message if failed")


class LockWorkspaceRequest(BaseModel):
    """Request to lock a workspace."""

    project_id: str = Field(..., description="Project ID")
    agent_id: str = Field(..., description="Agent ID requesting lock")
    timeout: int = Field(
        default=300, ge=10, le=3600, description="Lock timeout seconds"
    )


class LockWorkspaceResult(BaseModel):
    """Result of locking workspace."""

    success: bool = Field(..., description="Whether lock acquired")
    lock_holder: str | None = Field(default=None, description="Current lock holder")
    lock_expires: str | None = Field(
        default=None, description="Lock expiry ISO timestamp"
    )
    error: str | None = Field(default=None, description="Error message if failed")


class UnlockWorkspaceRequest(BaseModel):
    """Request to unlock a workspace."""

    project_id: str = Field(..., description="Project ID")
    agent_id: str = Field(..., description="Agent ID releasing lock")
    force: bool = Field(default=False, description="Force unlock (admin only)")


class UnlockWorkspaceResult(BaseModel):
    """Result of unlocking workspace."""

    success: bool = Field(..., description="Whether unlock succeeded")
    error: str | None = Field(default=None, description="Error message if failed")


# Health and Status


class HealthStatus(BaseModel):
    """Health status response."""

    status: str = Field(..., description="Health status")
    version: str = Field(..., description="Server version")
    workspace_count: int = Field(default=0, description="Active workspaces")
    gitea_connected: bool = Field(default=False, description="Gitea connectivity")
    github_connected: bool = Field(default=False, description="GitHub connectivity")
    gitlab_connected: bool = Field(default=False, description="GitLab connectivity")
    redis_connected: bool = Field(default=False, description="Redis connectivity")


class ProviderStatus(BaseModel):
    """Provider connection status."""

    provider: str = Field(..., description="Provider name")
    connected: bool = Field(..., description="Connection status")
    url: str | None = Field(default=None, description="Provider URL")
    user: str | None = Field(default=None, description="Authenticated user")
    error: str | None = Field(default=None, description="Error if not connected")
