"""Tests for dgtw_mlflow_utils.naming.

==============================
Covers: ProjectPrefix, Stage, Architecture, ModelVariant, RunAction enums,
        _validate_segment helper, and ModelNameBuilder class.
"""

import pytest
from dgtw_mlflow_utils.naming import (
    Architecture,
    ModelNameBuilder,
    ModelVariant,
    ProjectPrefix,
    RunAction,
    Stage,
    _validate_segment,
)

# ======================================================================
# Enum tests
# ======================================================================


class TestProjectPrefix:
    def test_tat_value(self):
        assert ProjectPrefix.TAT.value == "TAT"

    def test_is_str_enum(self):
        assert isinstance(ProjectPrefix.TAT, str)
        assert ProjectPrefix.TAT == "TAT"


class TestStage:
    def test_all_values(self):
        assert Stage.RESEARCH.value == "Research"
        assert Stage.STAGING.value == "Staging"
        assert Stage.PRODUCTION.value == "Production"
        assert Stage.EXPERIMENT.value == "Experiment"
        assert Stage.DEPRECATED.value == "Deprecated"

    def test_is_str_enum(self):
        assert isinstance(Stage.RESEARCH, str)
        assert Stage.RESEARCH == "Research"

    def test_member_count(self):
        assert len(Stage) == 5


class TestArchitecture:
    def test_all_values(self):
        assert Architecture.TWO_TOWER.value == "TwoTower"
        assert Architecture.RANKING.value == "Ranking"
        assert Architecture.SVD.value == "SVD"
        assert Architecture.MLP.value == "MLP"
        assert Architecture.TRANSFORMER.value == "Transformer"
        assert Architecture.BERT4REC.value == "Bert4Rec"
        assert Architecture.LIGHTGCN.value == "LightGCN"
        assert Architecture.CUSTOM.value == "Custom"

    def test_is_str_enum(self):
        assert isinstance(Architecture.TWO_TOWER, str)
        assert Architecture.TWO_TOWER == "TwoTower"

    def test_member_count(self):
        assert len(Architecture) == 8


class TestModelVariant:
    def test_all_values(self):
        assert ModelVariant.BASELINE.value == "Baseline"
        assert ModelVariant.CHAMPION.value == "Champion"
        assert ModelVariant.CHALLENGER.value == "Challenger"
        assert ModelVariant.CANARY.value == "Canary"
        assert ModelVariant.SHADOW.value == "Shadow"
        assert ModelVariant.ABLATION.value == "Ablation"

    def test_member_count(self):
        assert len(ModelVariant) == 6


class TestRunAction:
    def test_all_values(self):
        assert RunAction.TRAIN.value == "Train"
        assert RunAction.UPLOAD.value == "Upload"
        assert RunAction.EVALUATE.value == "Evaluate"
        assert RunAction.FINETUNE.value == "Finetune"
        assert RunAction.VALIDATE.value == "Validate"

    def test_member_count(self):
        assert len(RunAction) == 5


# ======================================================================
# _validate_segment tests
# ======================================================================


class TestValidateSegment:
    def test_valid_segment(self):
        assert _validate_segment("Hello", "test") == "Hello"

    def test_valid_with_underscore(self):
        assert _validate_segment("Two_Tower", "test") == "Two_Tower"

    def test_valid_with_hyphen(self):
        assert _validate_segment("Two-Tower", "test") == "Two-Tower"

    def test_valid_with_digits(self):
        assert _validate_segment("Model123", "test") == "Model123"

    def test_strips_whitespace(self):
        assert _validate_segment("  Hello  ", "test") == "Hello"

    def test_empty_string_raises(self):
        with pytest.raises(ValueError, match="must not be empty"):
            _validate_segment("", "test")

    def test_whitespace_only_raises(self):
        with pytest.raises(ValueError, match="must not be empty"):
            _validate_segment("   ", "test")

    def test_starts_with_hyphen_raises(self):
        with pytest.raises(ValueError, match="invalid characters"):
            _validate_segment("-hello", "test")

    def test_starts_with_underscore_raises(self):
        with pytest.raises(ValueError, match="invalid characters"):
            _validate_segment("_hello", "test")

    def test_special_characters_raises(self):
        with pytest.raises(ValueError, match="invalid characters"):
            _validate_segment("hello world", "test")

    def test_dot_raises(self):
        with pytest.raises(ValueError, match="invalid characters"):
            _validate_segment("hello.world", "test")


# ======================================================================
# ModelNameBuilder tests
# ======================================================================


class TestModelNameBuilder:
    """Tests for the main ModelNameBuilder class."""

    def setup_method(self):
        self.builder = ModelNameBuilder()

    # --- Default prefix ---

    def test_default_prefix(self):
        assert self.builder.prefix == "TAT"

    def test_custom_prefix(self):
        builder = ModelNameBuilder(prefix="CUSTOM")
        name = builder.experiment_name(component="Test", stage=Stage.RESEARCH)
        assert name == "CUSTOM_Test_Research"

    # --- experiment_name ---

    def test_experiment_name_with_enum(self):
        name = self.builder.experiment_name(component="Recommendation", stage=Stage.RESEARCH)
        assert name == "TAT_Recommendation_Research"

    def test_experiment_name_all_stages(self):
        for stage in Stage:
            name = self.builder.experiment_name(component="Test", stage=stage)
            assert name == f"TAT_Test_{stage.value}"

    def test_experiment_name_with_string_stage(self):
        name = self.builder.experiment_name(component="Test", stage="Beta")
        assert name == "TAT_Test_Beta"

    def test_experiment_name_default_component(self):
        name = self.builder.experiment_name()
        assert name == "TAT_General_Research"

    def test_experiment_name_none_component(self):
        name = self.builder.experiment_name(component=None)
        assert name == "TAT_General_Research"

    def test_experiment_name_invalid_stage_raises(self):
        with pytest.raises(ValueError):
            self.builder.experiment_name(component="Test", stage="  ")

    def test_experiment_name_invalid_component_raises(self):
        with pytest.raises(ValueError):
            self.builder.experiment_name(component=".invalid", stage=Stage.RESEARCH)

    # --- registered_model_name ---

    def test_registered_model_name_with_enums(self):
        name = self.builder.registered_model_name(
            architecture=Architecture.TWO_TOWER,
            variant=ModelVariant.BASELINE,
        )
        assert name == "TAT_TwoTower_Baseline"

    def test_registered_model_name_all_architectures(self):
        for arch in Architecture:
            name = self.builder.registered_model_name(architecture=arch, variant=ModelVariant.BASELINE)
            assert name == f"TAT_{arch.value}_Baseline"

    def test_registered_model_name_all_variants(self):
        for variant in ModelVariant:
            name = self.builder.registered_model_name(architecture=Architecture.TWO_TOWER, variant=variant)
            assert name == f"TAT_TwoTower_{variant.value}"

    def test_registered_model_name_with_custom_strings(self):
        name = self.builder.registered_model_name(architecture="MyTransformer", variant="v2")
        assert name == "TAT_MyTransformer_v2"

    def test_registered_model_name_defaults(self):
        name = self.builder.registered_model_name()
        assert name == "TAT_TwoTower_Baseline"

    def test_registered_model_name_invalid_architecture_raises(self):
        with pytest.raises(ValueError):
            self.builder.registered_model_name(architecture="", variant="v1")

    def test_registered_model_name_invalid_variant_raises(self):
        with pytest.raises(ValueError):
            self.builder.registered_model_name(architecture=Architecture.TWO_TOWER, variant=".bad")

    # --- run_name ---

    def test_run_name_with_fixed_timestamp(self):
        name = self.builder.run_name(
            action=RunAction.TRAIN,
            use_case="MiniLM",
            timestamp="20261025-1400",
        )
        assert name == "Train_MiniLM_20261025-1400"

    def test_run_name_all_actions(self):
        for action in RunAction:
            name = self.builder.run_name(action=action, use_case="Test", timestamp="20260101-0000")
            assert name == f"{action.value}_Test_20260101-0000"

    def test_run_name_with_string_action(self):
        name = self.builder.run_name(action="CustomAction", use_case="Model", timestamp="20260101-0000")
        assert name == "CustomAction_Model_20260101-0000"

    def test_run_name_auto_timestamp(self):
        name = self.builder.run_name(action=RunAction.TRAIN, use_case="Test")
        # Should contain the action and use_case, plus an auto-generated timestamp
        assert name.startswith("Train_Test_")
        # Timestamp format: YYYYMMDD-HHMM
        ts_part = name.split("_", 2)[2]
        assert len(ts_part) == 13  # e.g. "20260224-1400"

    def test_run_name_defaults(self):
        name = self.builder.run_name(timestamp="20260101-0000")
        assert name == "Train_Model_20260101-0000"

    def test_run_name_invalid_use_case_raises(self):
        with pytest.raises(ValueError):
            self.builder.run_name(action=RunAction.TRAIN, use_case="  ")

    # --- validate_registered_name ---

    def test_validate_valid_names(self):
        assert ModelNameBuilder.validate_registered_name("TAT_TwoTower_Baseline") is True
        assert ModelNameBuilder.validate_registered_name("TAT_Ranking_Champion") is True
        assert ModelNameBuilder.validate_registered_name("TAT_A_B_C") is True

    def test_validate_invalid_names(self):
        assert ModelNameBuilder.validate_registered_name("MyModel_v1") is False
        assert ModelNameBuilder.validate_registered_name("TAT") is False
        assert ModelNameBuilder.validate_registered_name("TAT_") is False
        assert ModelNameBuilder.validate_registered_name("") is False

    def test_validate_wrong_prefix(self):
        assert ModelNameBuilder.validate_registered_name("ABC_TwoTower_Baseline") is False
