"""Indexing state and reindex job tracking."""

from datetime import datetime
from enum import Enum

from pydantic import Field

from semantic_model.base import SemanticBaseModel, SourceId


class IndexingStatus(str, Enum):
    """Overall indexing status of the semantic model."""

    INDEXING = "indexing"
    PARTIAL = "partial"
    COMPLETE = "complete"
    STALE = "stale"


class ReindexJobType(str, Enum):
    """Types of reindex jobs."""

    FULL = "full"
    INCREMENTAL = "incremental"
    SOURCE_UPDATE = "source_update"
    EXPERT_FEEDBACK = "expert_feedback"
    CASCADE = "cascade"


class ReindexJobStatus(str, Enum):
    """Status of a reindex job."""

    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"


class ReindexTriggerType(str, Enum):
    """What triggered a reindex."""

    INITIAL_INDEX = "initial_index"
    SCHEMA_CHANGE = "schema_change"
    NEW_TABLE = "new_table"
    EXPERT_OVERRIDE = "expert_override"
    DEPENDENCY_CASCADE = "dependency_cascade"
    MANUAL = "manual"
    SCHEDULED = "scheduled"


class ReindexTrigger(SemanticBaseModel):
    """Details about what triggered a reindex."""

    type: ReindexTriggerType
    source_id: SourceId | None = Field(
        default=None,
        description="Source that triggered this reindex",
    )
    changed_objects: list[str] = Field(
        default_factory=list,
        description="Table/column IDs that changed",
    )
    expert_feedback_id: str | None = Field(
        default=None,
        description="If triggered by expert feedback",
    )


class ReindexJob(SemanticBaseModel):
    """A job to reindex part or all of the semantic model."""

    id: str = Field(..., description="Unique job ID")
    job_type: ReindexJobType
    status: ReindexJobStatus = Field(default=ReindexJobStatus.PENDING)
    triggered_by: ReindexTrigger
    
    # Scope
    target_source_ids: list[SourceId] = Field(
        default_factory=list,
        description="Sources to reindex",
    )
    cascade_to_source_ids: list[SourceId] = Field(
        default_factory=list,
        description="Sources that depend on targets and need reindex",
    )
    
    # Agent level
    level: int = Field(
        default=1,
        ge=1,
        le=2,
        description="Which agent level: 1 for source indexer, 2 for aggregator",
    )
    
    # Timing
    created_at: datetime = Field(default_factory=datetime.utcnow)
    started_at: datetime | None = Field(default=None)
    completed_at: datetime | None = Field(default=None)
    
    # Error handling
    error: str | None = Field(default=None)
    retry_count: int = Field(default=0)
    max_retries: int = Field(default=3)

    @property
    def is_terminal(self) -> bool:
        """Check if job is in a terminal state."""
        return self.status in (ReindexJobStatus.COMPLETED, ReindexJobStatus.FAILED)

    @property
    def can_retry(self) -> bool:
        """Check if job can be retried."""
        return (
            self.status == ReindexJobStatus.FAILED
            and self.retry_count < self.max_retries
        )

    def start(self) -> "ReindexJob":
        """Mark job as started."""
        return self.model_copy(
            update={
                "status": ReindexJobStatus.RUNNING,
                "started_at": datetime.utcnow(),
            }
        )

    def complete(self) -> "ReindexJob":
        """Mark job as completed."""
        return self.model_copy(
            update={
                "status": ReindexJobStatus.COMPLETED,
                "completed_at": datetime.utcnow(),
            }
        )

    def fail(self, error: str) -> "ReindexJob":
        """Mark job as failed."""
        return self.model_copy(
            update={
                "status": ReindexJobStatus.FAILED,
                "completed_at": datetime.utcnow(),
                "error": error,
            }
        )


class IndexingState(SemanticBaseModel):
    """Overall indexing state of the semantic model."""

    overall_status: IndexingStatus = Field(default=IndexingStatus.PARTIAL)
    last_full_index_at: datetime | None = Field(default=None)
    last_incremental_index_at: datetime | None = Field(default=None)
    pending_reindex_jobs: list[ReindexJob] = Field(default_factory=list)

    @property
    def has_pending_jobs(self) -> bool:
        """Check if there are pending reindex jobs."""
        return any(
            job.status in (ReindexJobStatus.PENDING, ReindexJobStatus.RUNNING)
            for job in self.pending_reindex_jobs
        )

    def add_job(self, job: ReindexJob) -> "IndexingState":
        """Add a new reindex job."""
        return self.model_copy(
            update={
                "pending_reindex_jobs": [*self.pending_reindex_jobs, job],
                "overall_status": IndexingStatus.INDEXING,
            }
        )

    def update_job(self, job: ReindexJob) -> "IndexingState":
        """Update an existing job in the list."""
        jobs = [j if j.id != job.id else job for j in self.pending_reindex_jobs]
        
        # Recalculate overall status
        if any(j.status == ReindexJobStatus.RUNNING for j in jobs):
            status = IndexingStatus.INDEXING
        elif all(j.status == ReindexJobStatus.COMPLETED for j in jobs):
            status = IndexingStatus.COMPLETE
        else:
            status = IndexingStatus.PARTIAL
        
        return self.model_copy(
            update={
                "pending_reindex_jobs": jobs,
                "overall_status": status,
            }
        )

    def cleanup_completed_jobs(self) -> "IndexingState":
        """Remove completed jobs from the pending list."""
        pending = [j for j in self.pending_reindex_jobs if not j.is_terminal]
        return self.model_copy(update={"pending_reindex_jobs": pending})
