"""
Abstract repository interfaces for data access patterns.

Defines the contracts for data access operations across different
data sources, enabling clean separation between business logic
and infrastructure concerns.
"""

from abc import ABC, abstractmethod
from typing import List, Optional, Dict, Any, AsyncContextManager
from pathlib import Path

from domain.issues.models import Issue
from domain.projects.models import Project, Milestone
from infrastructure.exceptions import ErrorContext


class IssueRepository(ABC):
    """Abstract repository for issue-related operations."""

    @abstractmethod
    async def get_issue(self, issue_number: int, context: Optional[ErrorContext] = None) -> Issue:
        """
        Retrieve an issue by its number.

        Args:
            issue_number: The issue number to retrieve
            context: Error context for tracking operations

        Returns:
            Issue domain object

        Raises:
            ResourceNotFoundError: If issue doesn't exist
            GiteaApiError: If API request fails
            NetworkError: If network connectivity fails
        """
        pass

    @abstractmethod
    async def get_issues(
        self,
        project_id: Optional[str] = None,
        state: Optional[str] = None,
        labels: Optional[List[str]] = None,
        limit: int = 100,
        offset: int = 0,
        context: Optional[ErrorContext] = None
    ) -> List[Issue]:
        """
        Retrieve multiple issues with filtering and pagination.

        Args:
            project_id: Filter by project ID
            state: Filter by issue state (open, closed)
            labels: Filter by labels
            limit: Maximum number of issues to return
            offset: Number of issues to skip
            context: Error context for tracking operations

        Returns:
            List of Issue domain objects

        Raises:
            GiteaApiError: If API request fails
            NetworkError: If network connectivity fails
        """
        pass

    @abstractmethod
    async def create_issue(
        self,
        title: str,
        body: str,
        labels: Optional[List[str]] = None,
        assignees: Optional[List[str]] = None,
        context: Optional[ErrorContext] = None
    ) -> Issue:
        """
        Create a new issue.

        Args:
            title: Issue title
            body: Issue description
            labels: List of label names
            assignees: List of assignee usernames
            context: Error context for tracking operations

        Returns:
            Created Issue domain object

        Raises:
            ValidationError: If input data is invalid
            GiteaApiError: If API request fails
            NetworkError: If network connectivity fails
        """
        pass

    @abstractmethod
    async def update_issue(
        self,
        issue_number: int,
        title: Optional[str] = None,
        body: Optional[str] = None,
        state: Optional[str] = None,
        labels: Optional[List[str]] = None,
        context: Optional[ErrorContext] = None
    ) -> Issue:
        """
        Update an existing issue.

        Args:
            issue_number: Issue number to update
            title: New title (if provided)
            body: New body (if provided)
            state: New state (if provided)
            labels: New labels (if provided)
            context: Error context for tracking operations

        Returns:
            Updated Issue domain object

        Raises:
            ResourceNotFoundError: If issue doesn't exist
            ValidationError: If input data is invalid
            GiteaApiError: If API request fails
            ConcurrencyError: If issue was modified concurrently
        """
        pass

    @abstractmethod
    async def get_issue_project_info(
        self,
        issue_number: int,
        context: Optional[ErrorContext] = None
    ) -> Dict[str, Any]:
        """
        Get project-related information for an issue.

        Args:
            issue_number: Issue number
            context: Error context for tracking operations

        Returns:
            Project information dictionary

        Raises:
            ResourceNotFoundError: If issue doesn't exist
            GiteaApiError: If API request fails
        """
        pass


class ProjectRepository(ABC):
    """Abstract repository for project-related operations."""

    @abstractmethod
    async def get_project(self, project_id: str, context: Optional[ErrorContext] = None) -> Project:
        """
        Retrieve a project by its ID.

        Args:
            project_id: Project identifier
            context: Error context for tracking operations

        Returns:
            Project domain object

        Raises:
            ResourceNotFoundError: If project doesn't exist
            GiteaApiError: If API request fails
        """
        pass

    @abstractmethod
    async def get_projects(
        self,
        organization: Optional[str] = None,
        limit: int = 100,
        offset: int = 0,
        context: Optional[ErrorContext] = None
    ) -> List[Project]:
        """
        Retrieve multiple projects with pagination.

        Args:
            organization: Filter by organization
            limit: Maximum number of projects to return
            offset: Number of projects to skip
            context: Error context for tracking operations

        Returns:
            List of Project domain objects

        Raises:
            GiteaApiError: If API request fails
        """
        pass

    @abstractmethod
    async def get_milestones(
        self,
        project_id: str,
        state: Optional[str] = None,
        context: Optional[ErrorContext] = None
    ) -> List[Milestone]:
        """
        Retrieve milestones for a project.

        Args:
            project_id: Project identifier
            state: Filter by milestone state
            context: Error context for tracking operations

        Returns:
            List of Milestone domain objects

        Raises:
            ResourceNotFoundError: If project doesn't exist
            GiteaApiError: If API request fails
        """
        pass

    @abstractmethod
    async def create_milestone(
        self,
        project_id: str,
        title: str,
        description: str,
        due_date: Optional[str] = None,
        context: Optional[ErrorContext] = None
    ) -> Milestone:
        """
        Create a new milestone for a project.

        Args:
            project_id: Project identifier
            title: Milestone title
            description: Milestone description
            due_date: Due date (ISO format)
            context: Error context for tracking operations

        Returns:
            Created Milestone domain object

        Raises:
            ResourceNotFoundError: If project doesn't exist
            ValidationError: If input data is invalid
            GiteaApiError: If API request fails
        """
        pass


class DocumentRepository(ABC):
    """Abstract repository for document storage and retrieval."""

    @abstractmethod
    async def store_document(
        self,
        filename: str,
        content: str,
        ast: Dict[str, Any],
        context: Optional[ErrorContext] = None
    ) -> str:
        """
        Store a document with its AST representation.

        Args:
            filename: Document filename
            content: Document content
            ast: Parsed AST representation
            context: Error context for tracking operations

        Returns:
            Document ID

        Raises:
            ValidationError: If input data is invalid
            DatabaseError: If storage operation fails
            DuplicateResourceError: If document already exists
        """
        pass

    @abstractmethod
    async def get_document(
        self,
        document_id: str,
        context: Optional[ErrorContext] = None
    ) -> Dict[str, Any]:
        """
        Retrieve a document by its ID.

        Args:
            document_id: Document identifier
            context: Error context for tracking operations

        Returns:
            Document data dictionary

        Raises:
            ResourceNotFoundError: If document doesn't exist
            DatabaseError: If retrieval operation fails
        """
        pass

    @abstractmethod
    async def get_documents(
        self,
        filename_pattern: Optional[str] = None,
        limit: int = 100,
        offset: int = 0,
        context: Optional[ErrorContext] = None
    ) -> List[Dict[str, Any]]:
        """
        Retrieve multiple documents with filtering and pagination.

        Args:
            filename_pattern: Filter by filename pattern
            limit: Maximum number of documents to return
            offset: Number of documents to skip
            context: Error context for tracking operations

        Returns:
            List of document data dictionaries

        Raises:
            DatabaseError: If retrieval operation fails
        """
        pass

    @abstractmethod
    async def update_document(
        self,
        document_id: str,
        content: Optional[str] = None,
        ast: Optional[Dict[str, Any]] = None,
        context: Optional[ErrorContext] = None
    ) -> Dict[str, Any]:
        """
        Update an existing document.

        Args:
            document_id: Document identifier
            content: New content (if provided)
            ast: New AST (if provided)
            context: Error context for tracking operations

        Returns:
            Updated document data

        Raises:
            ResourceNotFoundError: If document doesn't exist
            ValidationError: If input data is invalid
            DatabaseError: If update operation fails
        """
        pass

    @abstractmethod
    async def delete_document(
        self,
        document_id: str,
        context: Optional[ErrorContext] = None
    ) -> bool:
        """
        Delete a document.

        Args:
            document_id: Document identifier
            context: Error context for tracking operations

        Returns:
            True if document was deleted

        Raises:
            ResourceNotFoundError: If document doesn't exist
            DatabaseError: If deletion operation fails
        """
        pass

    @abstractmethod
    async def get_cache_path(
        self,
        document_id: str,
        context: Optional[ErrorContext] = None
    ) -> Path:
        """
        Get the cache file path for a document.

        Args:
            document_id: Document identifier
            context: Error context for tracking operations

        Returns:
            Path to cache file

        Raises:
            ResourceNotFoundError: If document doesn't exist
        """
        pass


class WorkspaceRepository(ABC):
    """Abstract repository for workspace file operations."""

    @abstractmethod
    async def create_workspace(
        self,
        workspace_id: str,
        base_path: Path,
        context: Optional[ErrorContext] = None
    ) -> Path:
        """
        Create a new workspace directory.

        Args:
            workspace_id: Workspace identifier
            base_path: Base directory for workspaces
            context: Error context for tracking operations

        Returns:
            Path to created workspace

        Raises:
            DuplicateResourceError: If workspace already exists
            ValidationError: If paths are invalid
            FileSystemError: If directory creation fails
        """
        pass

    @abstractmethod
    async def get_workspace_path(
        self,
        workspace_id: str,
        context: Optional[ErrorContext] = None
    ) -> Path:
        """
        Get the path to a workspace.

        Args:
            workspace_id: Workspace identifier
            context: Error context for tracking operations

        Returns:
            Path to workspace directory

        Raises:
            ResourceNotFoundError: If workspace doesn't exist
        """
        pass

    @abstractmethod
    async def list_workspaces(
        self,
        context: Optional[ErrorContext] = None
    ) -> List[str]:
        """
        List all available workspaces.

        Args:
            context: Error context for tracking operations

        Returns:
            List of workspace identifiers

        Raises:
            FileSystemError: If directory listing fails
        """
        pass

    @abstractmethod
    async def write_file(
        self,
        workspace_id: str,
        file_path: str,
        content: str,
        context: Optional[ErrorContext] = None
    ) -> Path:
        """
        Write content to a file in the workspace.

        Args:
            workspace_id: Workspace identifier
            file_path: Relative path within workspace
            content: File content
            context: Error context for tracking operations

        Returns:
            Full path to written file

        Raises:
            ResourceNotFoundError: If workspace doesn't exist
            ValidationError: If file path is invalid
            FileSystemError: If write operation fails
        """
        pass

    @abstractmethod
    async def read_file(
        self,
        workspace_id: str,
        file_path: str,
        context: Optional[ErrorContext] = None
    ) -> str:
        """
        Read content from a file in the workspace.

        Args:
            workspace_id: Workspace identifier
            file_path: Relative path within workspace
            context: Error context for tracking operations

        Returns:
            File content

        Raises:
            ResourceNotFoundError: If workspace or file doesn't exist
            FileSystemError: If read operation fails
        """
        pass

    @abstractmethod
    async def delete_workspace(
        self,
        workspace_id: str,
        context: Optional[ErrorContext] = None
    ) -> bool:
        """
        Delete a workspace and all its contents.

        Args:
            workspace_id: Workspace identifier
            context: Error context for tracking operations

        Returns:
            True if workspace was deleted

        Raises:
            ResourceNotFoundError: If workspace doesn't exist
            FileSystemError: If deletion fails
        """
        pass

    @abstractmethod
    async def list_files(
        self,
        workspace_id: str,
        pattern: Optional[str] = None,
        context: Optional[ErrorContext] = None
    ) -> List[str]:
        """
        List files in a workspace.

        Args:
            workspace_id: Workspace identifier
            pattern: File pattern to match
            context: Error context for tracking operations

        Returns:
            List of relative file paths

        Raises:
            ResourceNotFoundError: If workspace doesn't exist
            FileSystemError: If listing fails
        """
        pass


class CacheRepository(ABC):
    """Abstract repository for caching operations."""

    @abstractmethod
    async def get(
        self,
        key: str,
        context: Optional[ErrorContext] = None
    ) -> Optional[Any]:
        """
        Retrieve a value from cache.

        Args:
            key: Cache key
            context: Error context for tracking operations

        Returns:
            Cached value or None if not found

        Raises:
            CacheError: If cache operation fails
        """
        pass

    @abstractmethod
    async def set(
        self,
        key: str,
        value: Any,
        ttl: Optional[int] = None,
        context: Optional[ErrorContext] = None
    ) -> bool:
        """
        Store a value in cache.

        Args:
            key: Cache key
            value: Value to cache
            ttl: Time to live in seconds
            context: Error context for tracking operations

        Returns:
            True if value was stored

        Raises:
            CacheError: If cache operation fails
        """
        pass

    @abstractmethod
    async def delete(
        self,
        key: str,
        context: Optional[ErrorContext] = None
    ) -> bool:
        """
        Delete a value from cache.

        Args:
            key: Cache key
            context: Error context for tracking operations

        Returns:
            True if value was deleted

        Raises:
            CacheError: If cache operation fails
        """
        pass

    @abstractmethod
    async def invalidate_pattern(
        self,
        pattern: str,
        context: Optional[ErrorContext] = None
    ) -> int:
        """
        Invalidate cache entries matching a pattern.

        Args:
            pattern: Pattern to match (e.g., "user:*")
            context: Error context for tracking operations

        Returns:
            Number of invalidated entries

        Raises:
            CacheInvalidationError: If invalidation fails
        """
        pass

    @abstractmethod
    async def store_ast_cache(
        self,
        document_id: str,
        ast: Dict[str, Any],
        context: Optional[ErrorContext] = None
    ) -> bool:
        """
        Store AST cache for a document.

        Args:
            document_id: Document identifier
            ast: AST representation
            context: Error context for tracking operations

        Returns:
            True if cache was stored

        Raises:
            CacheError: If cache operation fails
        """
        pass