from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any, Dict, List, Optional

from synthneura.core.schemas import ClinicalTrial

RunMeta = Dict[str, str]


@dataclass(frozen=True)
class ChangeRecord:
    nct_id: str
    change_status: str
    changed_fields: List[str]
    before: Dict[str, Any]
    after: Dict[str, Any]


@dataclass(frozen=True)
class StoreResult:
    counts: Dict[str, int]
    changes: List[ChangeRecord]


class TrialSink(ABC):
    @abstractmethod
    def store_trials(
        self,
        trials: List[ClinicalTrial],
        raw_trials: List[Dict[str, Any]],
        run_meta: RunMeta,
    ) -> StoreResult:
        """
        Persist trials and return counts for new/updated/unchanged/failed.
        """
        raise NotImplementedError

    @abstractmethod
    def store_extractions(
        self,
        nct_id: str,
        endpoints: List[Any],
        biomarkers: List[Any],
        *,
        run_id: Optional[str] = None,
        ingested_at: Optional[str] = None,
        provider: Optional[str] = None,
        model: Optional[str] = None,
        prompt_version: Optional[str] = None,
        raw_response: Optional[str] = None,
        validation_status: Optional[str] = None,
        validation_errors: Optional[List[str]] = None,
        confidence: Optional[float] = None,
    ) -> None:
        """
        Persist extracted endpoints/biomarkers for a trial.
        """
        raise NotImplementedError

    @abstractmethod
    def store_extraction_failure(
        self,
        nct_id: str,
        *,
        run_id: Optional[str] = None,
        ingested_at: Optional[str] = None,
        provider: Optional[str] = None,
        model: Optional[str] = None,
        error: Optional[str] = None,
    ) -> None:
        """
        Persist extraction failure metadata for a trial.
        """
        raise NotImplementedError

    @abstractmethod
    def fetch_previous_run_time(self, current_run_id: str) -> Optional[str]:
        """
        Return the most recent ingested_at for any run other than current_run_id.
        """
        raise NotImplementedError

    @abstractmethod
    def fetch_diffs_between(
        self, start_time: Optional[str], end_time: Optional[str]
    ) -> List[ChangeRecord]:
        """
        Return ChangeRecords between two timestamps.
        """
        raise NotImplementedError

    @abstractmethod
    def fetch_previous_extraction(
        self, nct_id: str, current_run_id: str
    ) -> Optional[Dict[str, Any]]:
        """
        Return the most recent extraction for nct_id before current_run_id.
        """
        raise NotImplementedError

    @abstractmethod
    def store_extraction_diff(
        self,
        nct_id: str,
        *,
        run_id: str,
        ingested_at: str,
        endpoints_added: List[str],
        endpoints_removed: List[str],
        biomarkers_added: List[str],
        biomarkers_removed: List[str],
        prompt_version: Optional[str] = None,
    ) -> None:
        """
        Persist endpoint/biomarker diffs for a run.
        """
        raise NotImplementedError
