from __future__ import annotations

from datetime import datetime
from typing import Optional

from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, JSON, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.sql import func

from src.apps.base.models.base import Base


class SchedulerLog(Base):
    """
    Execution log entry for a single scheduler task run.

    Created at run start (status=RUNNING) and updated on completion.
    Child runs (e.g. per-merchant sub-tasks) link back via parent_log_id.

    Indexed columns are chosen to match the three most common admin UI
    queries: filter-by-task, filter-by-status, lookup-by-celery-task-id,
    and time-range scans.
    """

    __tablename__ = "scheduler_logs"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)

    # Matches SchedulerConfig.task_name — denormalised for fast filtering without JOIN
    task_name: Mapped[str] = mapped_column(String(200), nullable=False, index=True)

    # Snapshot of the display name at run time (survives config renames)
    display_name: Mapped[str] = mapped_column(String(200), nullable=False)

    # RUNNING | SUCCESS | FAILED | SKIPPED
    run_status: Mapped[str] = mapped_column(String(20), nullable=False, index=True)

    # CELERY_BEAT | MANUAL
    triggered_by: Mapped[str] = mapped_column(String(20), nullable=False)

    # Populated only when triggered_by = MANUAL
    triggered_by_admin_id: Mapped[Optional[int]] = mapped_column(
        Integer, ForeignKey("users.id"), nullable=True
    )

    # For sub-tasks / child runs initiated by a parent scheduler run
    parent_log_id: Mapped[Optional[int]] = mapped_column(
        Integer, ForeignKey("scheduler_logs.id"), nullable=True
    )

    started_at: Mapped[Optional[datetime]] = mapped_column(
        DateTime(timezone=True), nullable=True
    )
    completed_at: Mapped[Optional[datetime]] = mapped_column(
        DateTime(timezone=True), nullable=True
    )

    # Wall-clock duration computed on completion (completed_at - started_at)
    duration_ms: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)

    records_processed: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)

    # Free-text structured output captured from the task (truncated if large)
    log_output: Mapped[Optional[str]] = mapped_column(Text, nullable=True)

    # Human-readable error summary (populated on FAILED)
    error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)

    # Full Python traceback (populated on FAILED)
    traceback: Mapped[Optional[str]] = mapped_column(Text, nullable=True)

    # True when duration_ms > SchedulerConfig.alert_threshold_ms
    threshold_exceeded: Mapped[bool] = mapped_column(
        Boolean, nullable=False, default=False
    )

    # Celery AsyncResult task ID for cross-referencing worker logs
    celery_task_id: Mapped[Optional[str]] = mapped_column(
        String(200), nullable=True, index=True
    )

    # JSON array of merchant IDs if the task was scoped to specific merchants
    merchant_ids: Mapped[Optional[list]] = mapped_column(JSON, nullable=True)

    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), nullable=False
    )

    # Relationships
    triggered_by_admin: Mapped[Optional["User"]] = relationship(  # type: ignore[name-defined]
        "User", foreign_keys=[triggered_by_admin_id], lazy="select"
    )
    parent_log: Mapped[Optional["SchedulerLog"]] = relationship(
        "SchedulerLog",
        foreign_keys=[parent_log_id],
        remote_side="SchedulerLog.id",
        lazy="select",
    )
