"""
Map tests to Gherkin scenarios based on metadata.

Tests can declare their Gherkin metadata via docstrings:

```python
def test_coverage_check(self):
    '''
    Feature: Reservar turno veterinario
    Scenario: Verificar cobertura en zona disponible
    Tags: @smoke @coverage
    '''
```

Or via class docstrings:

```python
class TestCoverageFlow(ContractHTTPTestCase):
    """
    Feature: Reservar turno veterinario
    Tags: @coverage
    """
```
"""

import re
from typing import Optional
from dataclasses import dataclass


@dataclass
class TestGherkinMetadata:
    """Gherkin metadata extracted from a test."""
    feature: Optional[str] = None
    scenario: Optional[str] = None
    tags: list[str] = None

    def __post_init__(self):
        if self.tags is None:
            self.tags = []


def extract_gherkin_metadata(docstring: Optional[str]) -> TestGherkinMetadata:
    """
    Extract Gherkin metadata from a test docstring.

    Looks for:
    - Feature: <name>
    - Scenario: <name>
    - Tags: @tag1 @tag2

    Args:
        docstring: Test or class docstring

    Returns:
        TestGherkinMetadata with extracted info
    """
    if not docstring:
        return TestGherkinMetadata()

    # Extract Feature
    feature = None
    feature_match = re.search(r"Feature:\s*(.+)", docstring)
    if feature_match:
        feature = feature_match.group(1).strip()

    # Extract Scenario (also try Spanish: Escenario)
    scenario = None
    scenario_match = re.search(r"(Scenario|Escenario):\s*(.+)", docstring)
    if scenario_match:
        scenario = scenario_match.group(2).strip()

    # Extract Tags
    tags = []
    tags_match = re.search(r"Tags:\s*(.+)", docstring)
    if tags_match:
        tags_str = tags_match.group(1).strip()
        tags = re.findall(r"@[\w-]+", tags_str)

    return TestGherkinMetadata(
        feature=feature,
        scenario=scenario,
        tags=tags
    )


def has_gherkin_metadata(docstring: Optional[str]) -> bool:
    """Check if a docstring contains Gherkin metadata."""
    if not docstring:
        return False

    return bool(
        re.search(r"Feature:\s*", docstring) or
        re.search(r"Scenario:\s*", docstring) or
        re.search(r"Escenario:\s*", docstring) or
        re.search(r"Tags:\s*@", docstring)
    )


def match_test_to_feature(
    test_metadata: TestGherkinMetadata,
    feature_names: list[str]
) -> Optional[str]:
    """
    Match a test's feature metadata to an actual feature name.

    Uses fuzzy matching if exact match not found.

    Args:
        test_metadata: Extracted test metadata
        feature_names: List of available feature names

    Returns:
        Matched feature name or None
    """
    if not test_metadata.feature:
        return None

    # Exact match
    if test_metadata.feature in feature_names:
        return test_metadata.feature

    # Case-insensitive match
    test_feature_lower = test_metadata.feature.lower()
    for feature_name in feature_names:
        if feature_name.lower() == test_feature_lower:
            return feature_name

    # Partial match (feature name contains test feature or vice versa)
    for feature_name in feature_names:
        if test_feature_lower in feature_name.lower():
            return feature_name
        if feature_name.lower() in test_feature_lower:
            return feature_name

    return None


def match_test_to_scenario(
    test_metadata: TestGherkinMetadata,
    scenario_names: list[str]
) -> Optional[str]:
    """
    Match a test's scenario metadata to an actual scenario name.

    Uses fuzzy matching if exact match not found.

    Args:
        test_metadata: Extracted test metadata
        scenario_names: List of available scenario names

    Returns:
        Matched scenario name or None
    """
    if not test_metadata.scenario:
        return None

    # Exact match
    if test_metadata.scenario in scenario_names:
        return test_metadata.scenario

    # Case-insensitive match
    test_scenario_lower = test_metadata.scenario.lower()
    for scenario_name in scenario_names:
        if scenario_name.lower() == test_scenario_lower:
            return scenario_name

    # Partial match
    for scenario_name in scenario_names:
        if test_scenario_lower in scenario_name.lower():
            return scenario_name
        if scenario_name.lower() in test_scenario_lower:
            return scenario_name

    return None
