"""naming.py.

=========
Standardized naming convention builder for the TAT MLflow Model Registry.

Naming Standards (from mlflow_model_management.md):
----------------------------------------------------
  Experiment Names  :  TAT_{Component}_{Stage}
                       e.g. TAT_Recommendation_Research, TAT_Ranking_Production

  Registered Model  :  TAT_{Architecture}_{Type}
                       e.g. TAT_TwoTower_Baseline, TAT_TwoTower_Champions

  Run Names         :  {Action}_{UseCase}_{Timestamp}
                       e.g. Train_MiniLM_20261025-1400, Upload_TwoTower_20261025-1530
"""

from __future__ import annotations

import re
import time
from dataclasses import dataclass
from enum import StrEnum

# ---------------------------------------------------------------------------
# Enumerations for controlled vocabularies
# ---------------------------------------------------------------------------


class ProjectPrefix(StrEnum):
    """Top-level project prefix. All TAT names start with this."""

    TAT = "TAT"


class Stage(StrEnum):
    """Lifecycle stage of the experiment / model."""

    RESEARCH = "Research"
    STAGING = "Staging"
    PRODUCTION = "Production"
    EXPERIMENT = "Experiment"
    DEPRECATED = "Deprecated"


class Architecture(StrEnum):
    """Model architecture identifiers."""

    TWO_TOWER = "TwoTower"
    RANKING = "Ranking"
    SVD = "SVD"
    MLP = "MLP"
    TRANSFORMER = "Transformer"
    BERT4REC = "Bert4Rec"
    LIGHTGCN = "LightGCN"
    CUSTOM = "Custom"


class ModelVariant(StrEnum):
    """Variant or role of the registered model."""

    BASELINE = "Baseline"
    CHAMPION = "Champion"
    CHALLENGER = "Challenger"
    CANARY = "Canary"
    SHADOW = "Shadow"
    ABLATION = "Ablation"


class RunAction(StrEnum):
    """Action prefix for run names."""

    TRAIN = "Train"
    UPLOAD = "Upload"
    EVALUATE = "Evaluate"
    FINETUNE = "Finetune"
    VALIDATE = "Validate"


# ---------------------------------------------------------------------------
# Validation helper
# ---------------------------------------------------------------------------

_SAFE_SEGMENT_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_-]*$")


def _validate_segment(value: str, label: str) -> str:
    """Ensure a name segment uses only safe characters.

    Raises:
    ------
    ValueError
        If the segment is empty or contains invalid characters.
    """
    stripped = value.strip()
    if not stripped:
        raise ValueError(f"{label} must not be empty.")
    if not _SAFE_SEGMENT_RE.match(stripped):
        raise ValueError(
            f"{label} '{stripped}' contains invalid characters. "
            "Use only letters, digits, hyphens, or underscores, "
            "and must not start with a hyphen or underscore."
        )
    return stripped


# ---------------------------------------------------------------------------
# ModelNameBuilder
# ---------------------------------------------------------------------------


@dataclass
class ModelNameBuilder:
    """Fluent builder that generates MLflow names following the TAT standard.

    Examples:
    --------
    >>> builder = ModelNameBuilder()

    # Experiment name
    >>> builder.experiment_name(component="Recommendation", stage=Stage.RESEARCH)
    'TAT_Recommendation_Research'

    # Registered model name
    >>> builder.registered_model_name(
    ...     architecture=Architecture.TWO_TOWER,
    ...     variant=ModelVariant.BASELINE
    ... )
    'TAT_TwoTower_Baseline'

    # Run name  (timestamp is auto-generated)
    >>> builder.run_name(action=RunAction.TRAIN, use_case="MiniLM")
    'Train_MiniLM_20261025-1400'

    # Custom freeform experiment (when enums don't fit)
    >>> builder.experiment_name(component="ContextAware", stage="Beta")
    'TAT_ContextAware_Beta'
    """

    prefix: str = ProjectPrefix.TAT.value

    # ------------------------------------------------------------------
    # Experiment name
    # ------------------------------------------------------------------

    def experiment_name(
        self,
        component: str | None = None,
        stage: str | Stage = Stage.RESEARCH,
    ) -> str:
        """Build an experiment name: ``TAT_{Component}_{Stage}``.

        Parameters
        ----------
        component:
            Logical component of the system (e.g. 'Recommendation', 'Ranking').
        stage:
            Lifecycle stage. Accepts a :class:`Stage` enum or a raw string.

        Returns:
        -------
        str
            Formatted experiment name.
        """
        component_part = _validate_segment(component or "General", "component")
        stage_part = stage.value if isinstance(stage, Stage) else _validate_segment(str(stage), "stage")

        return f"{self.prefix}_{component_part}_{stage_part}"

    # ------------------------------------------------------------------
    # Registered model name
    # ------------------------------------------------------------------

    def registered_model_name(
        self,
        architecture: str | Architecture = Architecture.TWO_TOWER,
        variant: str | ModelVariant = ModelVariant.BASELINE,
    ) -> str:
        """Build a registered model name: ``TAT_{Architecture}_{Variant}``.

        Parameters
        ----------
        architecture:
            Model architecture identifier.  Accepts :class:`Architecture` enum
            or a custom string (e.g. ``"MyTransformer"``).
        variant:
            Model variant identifier. Accepts :class:`ModelVariant` enum
            or a custom string (e.g. ``"v2"``).

        Returns:
        -------
        str
            Formatted registered model name.
        """
        if isinstance(architecture, Architecture):
            arch_part = architecture.value
        else:
            arch_part = _validate_segment(str(architecture), "architecture")

        var_part = variant.value if isinstance(variant, ModelVariant) else _validate_segment(str(variant), "variant")

        return f"{self.prefix}_{arch_part}_{var_part}"

    # ------------------------------------------------------------------
    # Run name
    # ------------------------------------------------------------------

    def run_name(
        self,
        action: str | RunAction = RunAction.TRAIN,
        use_case: str = "Model",
        timestamp: str | None = None,
    ) -> str:
        """Build a run name: ``{Action}_{UseCase}_{Timestamp}``.

        Parameters
        ----------
        action:
            What the run is doing. Accepts :class:`RunAction` enum or a string.
        use_case:
            Short descriptor of this specific run (e.g. ``"MiniLM"``,
            ``"TwoTower"``).
        timestamp:
            Override timestamp (``YYYYMMDD-HHMM`` format). Auto-generated if
            omitted.

        Returns:
        -------
        str
            Formatted run name.
        """
        action_part = action.value if isinstance(action, RunAction) else _validate_segment(str(action), "action")

        use_case_part = _validate_segment(use_case, "use_case")

        if timestamp is None:
            timestamp = time.strftime("%Y%m%d-%H%M")

        return f"{action_part}_{use_case_part}_{timestamp}"

    # ------------------------------------------------------------------
    # Convenience: parse & validate an existing name
    # ------------------------------------------------------------------

    @staticmethod
    def validate_registered_name(name: str) -> bool:
        """Check whether *name* adheres to ``TAT_{Architecture}_{Variant}`` format.

        Returns:
        -------
        bool
        """
        parts = name.split("_")
        if len(parts) < 3:
            return False
        if parts[0] != "TAT":
            return False
        try:
            for p in parts:
                _validate_segment(p, "segment")
        except ValueError:
            return False
        return True

    def __repr__(self) -> str:  # pragma: no cover
        """Repr."""
        return f"ModelNameBuilder(prefix='{self.prefix}')"
