"""Port defining how the application accesses technique data.

This is a domain contract — implementations live in infrastructure/.
The domain layer NEVER imports the implementation.
"""

# Enable future language features for compatibility
from __future__ import annotations

# Import uuid
import uuid

# Import NamedTuple, Protocol, runtime_checkable from typing
from typing import NamedTuple, Protocol, runtime_checkable

# Import TechniqueEntity from app.domain.entities.technique
from app.domain.entities.technique import TechniqueEntity

# Import TechniqueStatus from app.domain.enums
from app.domain.enums import TechniqueStatus


# Define class TechniqueWithCounts
class TechniqueWithCounts(NamedTuple):
    """Pre-aggregated technique data for heatmap/scoring."""

    # entity: TechniqueEntity
    entity: TechniqueEntity
    # test_count: int
    test_count: int
    # validated_test_count: int
    validated_test_count: int
    # detection_rule_count: int
    detection_rule_count: int


# Apply the @runtime_checkable decorator
@runtime_checkable
# Define class TechniqueRepository
class TechniqueRepository(Protocol):
    """Data access contract for techniques (one per aggregate root)."""

    # -- Single-entity access ----------------------------------------------

    def find_by_id(self, technique_id: uuid.UUID) -> TechniqueEntity | None:
        """Return the technique with the given primary key, or None if absent.

        Args:
            technique_id (uuid.UUID): Primary key of the technique to look up.

        Returns:
            TechniqueEntity | None: The matching entity, or None if not found.
        """
        # ...
        ...

    # Define function find_by_mitre_id
    def find_by_mitre_id(self, mitre_id: str) -> TechniqueEntity | None:
        """Return the technique matching the given MITRE ATT&CK identifier, or None.

        Args:
            mitre_id (str): MITRE ATT&CK ID (e.g. ``"T1059"`` or ``"T1059.001"``).

        Returns:
            TechniqueEntity | None: The matching entity, or None if not found.
        """
        # ...
        ...

    # -- List access -------------------------------------------------------

    def list_all(
        self,
        *,
        # Entry: tactic
        tactic: str | None = None,
        # Entry: status
        status: TechniqueStatus | None = None,
        # Entry: review_required
        review_required: bool | None = None,
    ) -> list[TechniqueEntity]:
        """Return all techniques, optionally filtered by tactic, status, or review flag.

        Args:
            tactic (str | None): When provided, restrict results to this tactic category.
            status (TechniqueStatus | None): When provided, restrict results to this status.
            review_required (bool | None): When provided, restrict results to techniques
                whose ``review_required`` flag matches this value.

        Returns:
            list[TechniqueEntity]: Matching technique entities; may be empty.
        """
        # ...
        ...

    # Define function list_by_ids
    def list_by_ids(self, ids: list[uuid.UUID]) -> list[TechniqueEntity]:
        """Return all techniques whose primary keys are in *ids*.

        Args:
            ids (list[uuid.UUID]): List of technique UUIDs to retrieve.

        Returns:
            list[TechniqueEntity]: Entities found for the supplied IDs; order
                is not guaranteed and missing IDs are silently omitted.
        """
        # ...
        ...

    # -- Batch queries (scoring/heatmap performance) -----------------------

    def count_by_status(self) -> dict[TechniqueStatus, int]:
        """Return a count of techniques grouped by their global status.

        Returns:
            dict[TechniqueStatus, int]: Mapping from each status value to the
                number of techniques in that state.
        """
        # ...
        ...

    # Define function find_all_with_test_counts
    def find_all_with_test_counts(self) -> list[TechniqueWithCounts]:
        """Return all techniques together with pre-aggregated test and rule counts.

        Returns:
            list[TechniqueWithCounts]: Each element bundles a TechniqueEntity
                with its total, validated, and detection-rule counts for use
                in heatmap and scoring calculations.
        """
        # ...
        ...

    # -- Mutations ---------------------------------------------------------

    def save(self, technique: TechniqueEntity) -> TechniqueEntity:
        """Persist a technique entity and return the saved state.

        Args:
            technique (TechniqueEntity): The entity to create or update.

        Returns:
            TechniqueEntity: The persisted entity, potentially with updated
                fields (e.g. server-side timestamps).
        """
        # ...
        ...

    # Define function exists_by_mitre_id
    def exists_by_mitre_id(self, mitre_id: str) -> bool:
        """Return True if a technique with the given MITRE ID exists in the repository.

        Args:
            mitre_id (str): MITRE ATT&CK ID to check (e.g. ``"T1059"``).

        Returns:
            bool: True if a matching technique is found, False otherwise.
        """
        # ...
        ...
