import logging
from abc import ABC, abstractmethod
from typing import List, Optional

from xulcan.core.primitives import MachineID
from xulcan.kernel.interfaces import LedgerRepository
from xulcan.history.events import RunEvent, RunSummary, RunStatus, EventType

logger = logging.getLogger(__name__)

class BaseLedgerAdapter(LedgerRepository, ABC):
    """Abstract base class for all Ledger storage providers.
    
    Provides a Template Method for logging database operations and a 
    Default Implementation for computing the RunSummary (The 'Fold') 
    so new adapters only need to implement basic read/write logic.
    """

    # ════════════════════════════════════════════════════════════════════════
    # TEMPLATE METHODS (Cross-Cutting Concerns)
    # ════════════════════════════════════════════════════════════════════════

    async def append(self, event: RunEvent) -> None:
        """Template method that intercepts writes for logging and telemetry."""
        # Aquí puedes meter métricas de Datadog/Prometheus en el futuro
        logger.debug(f"💾 Guardando evento {event.type.value} [Seq: {event.sequence_number}] para Run {event.run_id}")
        await self._append_impl(event)

    async def get_events(self, run_id: MachineID, from_index: int = 0) -> List[RunEvent]:
        """Template method for reading events."""
        return await self._get_events_impl(run_id, from_index)

    async def tag_run(self, run_id: MachineID, session_key: str) -> None:
        """Template method: etiqueta un run con una session_key legible."""
        logger.debug(f"🏷️  Sesión '{session_key}' → Run {run_id}")
        await self._tag_run_impl(run_id, session_key)

    async def get_last_run_id(self, session_key: str) -> Optional[MachineID]:
        """Template method: devuelve el último run_id de una sesión."""
        return await self._get_last_run_id_impl(session_key)


    # ════════════════════════════════════════════════════════════════════════
    # ABSTRACT METHODS (To be implemented by Subclasses)
    # ════════════════════════════════════════════════════════════════════════

    @abstractmethod
    async def _append_impl(self, event: RunEvent) -> None:
        """Actual storage logic (SQL INSERT, Redis push, Dict append, etc)."""
        pass

    @abstractmethod
    async def _get_events_impl(self, run_id: MachineID, from_index: int) -> List[RunEvent]:
        """Actual retrieval logic (SQL SELECT, Dict slice, etc)."""
        pass

    @abstractmethod
    async def _tag_run_impl(self, run_id: MachineID, session_key: str) -> None:
        """Persiste la relación session_key → run_id."""
        pass

    @abstractmethod
    async def _get_last_run_id_impl(self, session_key: str) -> Optional[MachineID]:
        """Recupera el último run_id asociado a una session_key."""
        pass


    # ════════════════════════════════════════════════════════════════════════
    # DEFAULT IMPLEMENTATION (The Fold)
    # ════════════════════════════════════════════════════════════════════════

    async def get_summary(self, run_id: MachineID) -> RunSummary:
        """Default Event Sourcing projection.
        
        Pulls events and computes the state in Python. Subclasses CAN override 
        this if their underlying database can do it faster (e.g., Postgres SQL).
        """
        events = await self.get_events(run_id)
        if not events:
            raise KeyError(f"Run {run_id} no encontrado en el Ledger.")
        
        first_event = events[0]
        last_event = events[-1]

        # --- THE FOLD LOGIC ---
        status = RunStatus.RUNNING
        
        if last_event.type == EventType.RUN_CREATED:
            status = RunStatus.CREATED
        elif last_event.type == EventType.RUN_COMPLETED:
            status = RunStatus.COMPLETED
        elif last_event.type == EventType.RUN_FAILED:
            status = RunStatus.FAILED
        elif last_event.type == EventType.HUMAN_INTERVENTION_REQUIRED:
            status = RunStatus.PAUSED

        return RunSummary(
            run_id=run_id,
            status=status,
            step_count=len(events),
            created_at=first_event.timestamp,
            updated_at=last_event.timestamp,
            last_event=last_event
        )