# Copyright 2026 Terrene Foundation
# SPDX-License-Identifier: Apache-2.0

"""
EATP Trust Store — ABC, TransactionContext, and filesystem utilities.

Provides the abstract interface for trust chain storage and
transactional context for atomic operations.

Architecture:
- TrustStore ABC defines the contract for all store implementations
- TransactionContext enables atomic multi-chain updates (CARE-008)
- Concrete implementations live in submodules (memory, filesystem, etc.)

Public utilities available via ``from kailash.trust.chain_store.filesystem import ...``:
- ``file_lock``: Cross-process file lock context manager (fcntl.flock)
- ``validate_id``: Path-traversal-safe identifier validation
"""

from __future__ import annotations

import copy
from abc import ABC, abstractmethod
from datetime import datetime
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple

if TYPE_CHECKING:
    from kailash.trust.chain_store.memory import InMemoryTrustStore

from kailash.trust.chain import TrustLineageChain
from kailash.trust.exceptions import TrustChainNotFoundError


def _chain_has_missing_reasoning(chain: TrustLineageChain) -> bool:
    """
    Check if a trust chain has delegations or audit anchors missing reasoning traces.

    Returns True if at least one delegation or audit_trail entry lacks a
    reasoning_trace. Returns False if there are no delegations/audit_trail
    entries (nothing to be missing) or all have reasoning.

    Args:
        chain: The TrustLineageChain to check.

    Returns:
        True if any delegation or audit anchor is missing a reasoning trace.
    """
    has_items = False

    for delegation in chain.delegations:
        has_items = True
        if delegation.reasoning_trace is None:
            return True

    for anchor in chain.audit_anchors:
        has_items = True
        if anchor.reasoning_trace is None:
            return True

    return False


class TrustStore(ABC):
    """
    Abstract base class for EATP trust chain storage.

    Defines the interface that all trust store implementations must follow.
    Provides CRUD operations for TrustLineageChain objects with filtering
    and pagination support.

    Implementations:
    - InMemoryTrustStore: Fast in-memory storage for testing/development
    - FilesystemStore: Persistent JSON-file storage (``eatp.store.filesystem``)
    - SqliteTrustStore: Persistent SQLite storage (``eatp.store.sqlite``)
    - PostgresTrustStore: Production storage using DataFlow (in kailash-kaizen)
    """

    @abstractmethod
    async def initialize(self) -> None:
        """Initialize the trust store."""
        ...

    @abstractmethod
    async def store_chain(
        self,
        chain: TrustLineageChain,
        expires_at: Optional[datetime] = None,
    ) -> str:
        """
        Store a trust lineage chain.

        Args:
            chain: The TrustLineageChain to store
            expires_at: Optional expiration datetime

        Returns:
            The agent_id of the stored chain
        """
        ...

    @abstractmethod
    async def get_chain(
        self,
        agent_id: str,
        include_inactive: bool = False,
    ) -> TrustLineageChain:
        """
        Retrieve a trust lineage chain by agent_id.

        Args:
            agent_id: The agent ID to retrieve
            include_inactive: Include inactive chains

        Returns:
            The TrustLineageChain for the agent

        Raises:
            TrustChainNotFoundError: If chain not found
        """
        ...

    @abstractmethod
    async def update_chain(
        self,
        agent_id: str,
        chain: TrustLineageChain,
    ) -> None:
        """
        Update an existing trust lineage chain.

        Args:
            agent_id: The agent ID to update
            chain: The new TrustLineageChain data

        Raises:
            TrustChainNotFoundError: If chain not found
        """
        ...

    @abstractmethod
    async def delete_chain(
        self,
        agent_id: str,
        soft_delete: bool = True,
    ) -> None:
        """
        Delete a trust lineage chain.

        Args:
            agent_id: The agent ID to delete
            soft_delete: Use soft delete vs hard delete

        Raises:
            TrustChainNotFoundError: If chain not found
        """
        ...

    @abstractmethod
    async def list_chains(
        self,
        authority_id: Optional[str] = None,
        active_only: bool = True,
        limit: int = 100,
        offset: int = 0,
    ) -> List[TrustLineageChain]:
        """
        List trust lineage chains with filtering and pagination.

        Args:
            authority_id: Filter by authority ID (optional)
            active_only: Include only active chains
            limit: Maximum number of results
            offset: Offset for pagination

        Returns:
            List of TrustLineageChain objects
        """
        ...

    @abstractmethod
    async def count_chains(
        self,
        authority_id: Optional[str] = None,
        active_only: bool = True,
    ) -> int:
        """
        Count trust lineage chains with filtering.

        Args:
            authority_id: Filter by authority ID (optional)
            active_only: Include only active chains

        Returns:
            Number of matching chains
        """
        ...

    @abstractmethod
    async def close(self) -> None:
        """Close and cleanup resources."""
        ...

    async def get_chains_missing_reasoning(self) -> List[str]:
        """
        Return agent IDs whose chains have delegations or audit anchors
        without reasoning traces.

        This is a compliance query: "show all agents without complete
        reasoning traces". An agent is included if ANY of its delegations
        or audit_trail entries lack a reasoning_trace.

        Agents whose chains have no delegations AND no audit_trail entries
        are NOT included (there is nothing to be missing).

        Returns:
            List of agent_id strings for chains with missing reasoning.
        """
        chains = await self.list_chains(active_only=True, limit=100000)
        missing: List[str] = []
        for chain in chains:
            if _chain_has_missing_reasoning(chain):
                missing.append(chain.genesis.agent_id)
        return missing


class TransactionContext:
    """
    Transactional context for trust store operations.

    CARE-008: Provides atomic update guarantees for trust chain re-signing.
    Collects store operations and applies them atomically on commit.
    On rollback (exception or no commit), restores the original state.

    Example:
        >>> async with store.transaction() as tx:
        ...     await tx.update_chain("agent-1", chain1)
        ...     await tx.update_chain("agent-2", chain2)
        ...     await tx.commit()  # Both updates applied atomically

    If an exception occurs or commit() is not called, all changes are discarded.
    """

    def __init__(self, store: "InMemoryTrustStore"):
        """
        Initialize TransactionContext.

        Args:
            store: The InMemoryTrustStore instance to operate on
        """
        from kailash.trust.chain_store.memory import InMemoryTrustStore

        self._store = store
        self._pending_updates: List[Tuple[str, TrustLineageChain]] = []
        self._committed = False
        self._snapshot: Dict[str, TrustLineageChain] = {}
        self._entered = False

    async def __aenter__(self) -> "TransactionContext":
        """
        Enter the transaction context and take a snapshot of current state.

        Returns:
            Self for use in async with statement
        """
        # Take deep copy snapshot of current state for rollback
        self._snapshot = {k: copy.deepcopy(v) for k, v in self._store._chains.items()}
        self._entered = True
        return self

    async def __aexit__(
        self,
        exc_type: Optional[type],
        exc_val: Optional[BaseException],
        exc_tb: Optional[Any],
    ) -> bool:
        """
        Exit the transaction context.

        On exception or if not committed, restores the original state (rollback).

        Args:
            exc_type: Exception type if raised
            exc_val: Exception value if raised
            exc_tb: Exception traceback if raised

        Returns:
            False to propagate exceptions
        """
        if exc_type is not None or not self._committed:
            # Rollback: restore snapshot
            self._store._chains.clear()
            self._store._chains.update(self._snapshot)
        return False

    async def update_chain(self, agent_id: str, chain: TrustLineageChain) -> None:
        """
        Queue a chain update for atomic commit.

        The update is not applied immediately; it's stored in the pending list
        and applied when commit() is called.

        Args:
            agent_id: The agent ID for the chain
            chain: The updated TrustLineageChain
        """
        if not self._entered:
            raise RuntimeError("TransactionContext must be used with 'async with'")
        self._pending_updates.append((agent_id, chain))

    async def commit(self) -> None:
        """
        Commit all pending updates atomically.

        Applies all queued updates to the store. After commit, the transaction
        is marked as committed and rollback will not occur on exit.

        Raises:
            RuntimeError: If called outside of context manager
        """
        if not self._entered:
            raise RuntimeError("TransactionContext must be used with 'async with'")

        # Apply all pending updates
        for agent_id, chain in self._pending_updates:
            await self._store.store_chain(chain)

        self._committed = True

    @property
    def pending_count(self) -> int:
        """Return the number of pending updates."""
        return len(self._pending_updates)
