"""Test data factory for Behave tests using Faker for realistic data generation.

This module provides factory functions and classes for generating realistic test data
for Behave scenarios. It uses Faker to generate dynamic, varied test data instead of
hardcoded values, improving test realism and maintainability.

Usage:
    from features.test_data_factory import ContextFragmentFactory, TestDataGenerator

    # Generate a single context fragment with realistic data
    fragment = ContextFragmentFactory.create()

    # Generate multiple fragments with varied data
    fragments = ContextFragmentFactory.create_batch(5)

    # Generate realistic project names
    project_name = TestDataGenerator.project_name()

    # Generate realistic Python code snippets
    code = TestDataGenerator.python_code()
"""

from __future__ import annotations

import sys
from pathlib import Path
from typing import Any

from faker import Faker

# Add src to path for imports
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
    sys.path.insert(0, _SRC)

from cleveragents.domain.models.core.context_fragment import (  # noqa: E402
    ContextBudget,
    ContextFragment,
    FragmentProvenance,
)

# Initialize Faker for consistent, reproducible test data
_faker = Faker()


class TestDataGenerator:
    """Generate realistic test data for various domain objects."""

    @staticmethod
    def project_name() -> str:
        """Generate a realistic project name."""
        return _faker.word() + "-" + _faker.word()

    @staticmethod
    def skill_name() -> str:
        """Generate a realistic skill name."""
        return _faker.word() + "_" + _faker.word()

    @staticmethod
    def tool_name() -> str:
        """Generate a realistic tool name."""
        return _faker.word() + "-tool"

    @staticmethod
    def actor_name() -> str:
        """Generate a realistic actor name."""
        return _faker.first_name() + _faker.last_name()

    @staticmethod
    def python_code() -> str:
        """Generate a realistic Python code snippet."""
        var_name = _faker.word()
        value = _faker.random_int(min=1, max=1000)
        return f"{var_name} = {value}"

    @staticmethod
    def python_prose() -> str:
        """Generate realistic Python-like prose."""
        return _faker.sentence(nb_words=10)

    @staticmethod
    def file_path() -> str:
        """Generate a realistic file path."""
        return f"src/{_faker.word()}/{_faker.word()}.py"

    @staticmethod
    def uko_node_uri() -> str:
        """Generate a realistic UKO node URI."""
        return f"p://{_faker.word()}/{_faker.word()}.py"

    @staticmethod
    def resource_uri() -> str:
        """Generate a realistic resource URI."""
        return f"test://{_faker.word()}-{_faker.word()}"

    @staticmethod
    def namespaced_name() -> tuple[str, str]:
        """Generate a realistic namespaced name (namespace, name)."""
        namespace = _faker.word()
        name = _faker.word() + "-" + _faker.word()
        return (namespace, name)

    @staticmethod
    def ulid() -> str:
        """Generate a valid ULID-like string (26 chars, base32)."""
        # Generate a valid Crockford base32 ULID (26 characters)
        # Using only valid characters: 0-9, A-Z (excluding I, L, O, U)
        valid_chars = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
        return "".join(_faker.random.choice(valid_chars) for _ in range(26))

    @staticmethod
    def relevance_score() -> float:
        """Generate a realistic relevance score (0.0 to 1.0)."""
        return round(_faker.pyfloat(min_value=0.0, max_value=1.0, right_digits=2), 2)

    @staticmethod
    def token_count() -> int:
        """Generate a realistic token count."""
        return _faker.random_int(min=10, max=500)

    @staticmethod
    def detail_depth() -> int:
        """Generate a realistic detail depth level."""
        return _faker.random_int(min=1, max=10)


class ContextFragmentFactory:
    """Factory for creating realistic ContextFragment test objects."""

    @staticmethod
    def create(**overrides: Any) -> ContextFragment:
        """Create a single ContextFragment with realistic default data.

        Args:
            **overrides: Override any default values

        Returns:
            A ContextFragment instance with realistic data
        """
        defaults = {
            "uko_node": TestDataGenerator.uko_node_uri(),
            "content": TestDataGenerator.python_code(),
            "token_count": TestDataGenerator.token_count(),
            "relevance_score": TestDataGenerator.relevance_score(),
            "detail_depth": TestDataGenerator.detail_depth(),
            "provenance": FragmentProvenance(
                resource_uri=TestDataGenerator.resource_uri()
            ),
        }
        defaults.update(overrides)
        return ContextFragment(**defaults)

    @staticmethod
    def create_batch(count: int, **overrides: Any) -> list[ContextFragment]:
        """Create multiple ContextFragments with varied realistic data.

        Args:
            count: Number of fragments to create
            **overrides: Override any default values (applied to all)

        Returns:
            A list of ContextFragment instances
        """
        return [ContextFragmentFactory.create(**overrides) for _ in range(count)]

    @staticmethod
    def create_with_content(content: str, **overrides: Any) -> ContextFragment:
        """Create a ContextFragment with specific content.

        Args:
            content: The content to use
            **overrides: Override any other default values

        Returns:
            A ContextFragment instance with the specified content
        """
        overrides["content"] = content
        return ContextFragmentFactory.create(**overrides)

    @staticmethod
    def create_duplicate_pair(
        content: str = "duplicate_content", **overrides: Any
    ) -> tuple[ContextFragment, ContextFragment]:
        """Create a pair of duplicate ContextFragments for deduplication testing.

        Args:
            content: The shared content
            **overrides: Override any other default values

        Returns:
            A tuple of two ContextFragments with identical content
        """
        frag1 = ContextFragmentFactory.create_with_content(content, **overrides)
        frag2 = ContextFragmentFactory.create_with_content(
            content,
            uko_node=frag1.uko_node,
            relevance_score=round(frag1.relevance_score - 0.1, 2),
            **overrides,
        )
        return (frag1, frag2)


class ContextBudgetFactory:
    """Factory for creating realistic ContextBudget test objects."""

    @staticmethod
    def create(
        max_tokens: int | None = None,
        reserved_tokens: int | None = None,
    ) -> ContextBudget:
        """Create a ContextBudget with realistic default data.

        Args:
            max_tokens: Maximum tokens (default: random 100-1000)
            reserved_tokens: Reserved tokens (default: 0)

        Returns:
            A ContextBudget instance
        """
        if max_tokens is None:
            max_tokens = _faker.random_int(min=100, max=1000)
        if reserved_tokens is None:
            reserved_tokens = 0
        return ContextBudget(max_tokens=max_tokens, reserved_tokens=reserved_tokens)

    @staticmethod
    def create_tight() -> ContextBudget:
        """Create a ContextBudget with tight constraints (100-200 tokens)."""
        max_tokens = _faker.random_int(min=100, max=200)
        return ContextBudgetFactory.create(max_tokens=max_tokens)

    @staticmethod
    def create_generous() -> ContextBudget:
        """Create a ContextBudget with generous constraints (500-2000 tokens)."""
        max_tokens = _faker.random_int(min=500, max=2000)
        return ContextBudgetFactory.create(max_tokens=max_tokens)
