"""
Semantic Layer for Outhad_Edge Natural Language Query.

Provides business-friendly descriptions of data schema, metrics, and analysis tools.
This layer helps the LLM understand the domain context and generate accurate code.
"""

from typing import Dict, List, Any, Optional
import pandas as pd
from datetime import datetime

try:
    from pydantic import BaseModel
except ImportError:
    # Fallback if pydantic v2 not available
    try:
        from pydantic.v1 import BaseModel
    except ImportError:
        # Basic substitute for very old pydantic
        class BaseModel:
            pass


class ColumnMetadata(BaseModel):
    """Metadata for a single column"""
    name: str
    description: str
    data_type: str
    examples: List[str]
    constraints: Optional[Dict] = None

    class Config:
        arbitrary_types_allowed = True


class TableMetadata(BaseModel):
    """Metadata for a table/dataset"""
    name: str
    description: str
    columns: List[ColumnMetadata]
    relationships: Optional[Dict] = None
    business_terms: List[str] = []

    class Config:
        arbitrary_types_allowed = True


class SemanticLayer:
    """
    Semantic layer that provides business-friendly descriptions
    of data structures for LLM consumption.

    Combines:
    - Schema metadata with pydantic models
    - Business glossary (business terms → technical definitions)
    - API documentation for OuthadEdge operations
    - Runtime data statistics
    """

    def __init__(self, eventstream):
        """
        Initialize semantic layer with an eventstream.

        Args:
            eventstream: Outhad_Edge Eventstream object
        """
        self.eventstream = eventstream
        self.df = eventstream.to_dataframe()
        self.schema = eventstream.schema

        # Initialize metadata
        self.tables = self._initialize_metadata()
        self.business_glossary = self._initialize_glossary()

    def _initialize_metadata(self) -> Dict[str, TableMetadata]:
        """
        Define metadata for OuthadEdge Eventstream
        """
        return {
            'eventstream': TableMetadata(
                name='eventstream',
                description='User event data containing clickstream, behavior, and transaction events',
                columns=[
                    ColumnMetadata(
                        name='user_id',
                        description='Unique identifier for each user',
                        data_type='string',
                        examples=['user_123', 'anon_456'],
                        constraints={'not_null': True}
                    ),
                    ColumnMetadata(
                        name='event',
                        description='Type of event (e.g., page_view, purchase, add_to_cart)',
                        data_type='string',
                        examples=['page_view', 'purchase', 'add_to_cart', 'signup'],
                    ),
                    ColumnMetadata(
                        name='timestamp',
                        description='When the event occurred (datetime)',
                        data_type='datetime',
                        examples=['2024-01-15 10:30:00'],
                    ),
                    ColumnMetadata(
                        name='event_type',
                        description='Category of event (raw, synthetic, processed)',
                        data_type='string',
                        examples=['raw', 'path_start', 'path_end'],
                    ),
                    ColumnMetadata(
                        name='event_index',
                        description='Sequential order of events per user',
                        data_type='integer',
                        examples=['1', '2', '3'],
                    ),
                ],
                business_terms=['users', 'events', 'clickstream', 'behavior', 'journey']
            )
        }

    def _initialize_glossary(self) -> Dict[str, str]:
        """
        Business terms → technical definitions
        """
        return {
            'conversion': 'User completing a purchase or desired action',
            'conversion rate': 'Percentage of users who complete desired action',
            'funnel': 'Sequential steps users take toward a goal',
            'cohort': 'Group of users who share a common characteristic',
            'retention': 'Users who return after initial visit',
            'churn': 'Users who stop using the product',
            'session': 'Single continuous period of user activity',
            'journey': 'Complete path a user takes through the product',
            'touchpoint': 'Interaction point between user and product',
            'mobile': 'Events from mobile devices',
            'desktop': 'Events from desktop computers',
            'anonymous': 'Users without identified credentials',
            'mau': 'Monthly Active Users - unique users in a month',
            'dau': 'Daily Active Users - unique users in a day',
            'ltv': 'Lifetime Value - total revenue from a user',
            'arpu': 'Average Revenue Per User',
        }

    def get_schema_description(self, table: str = 'eventstream') -> str:
        """
        Generate natural language description of schema for LLM
        """
        metadata = self.tables[table]

        description = f"""
Table: {metadata.name}
Description: {metadata.description}

Columns:
"""
        for col in metadata.columns:
            description += f"\n- {col.name} ({col.data_type}): {col.description}"
            if col.examples:
                description += f"\n  Examples: {', '.join(col.examples)}"

        description += f"\n\nBusiness Terms: {', '.join(metadata.business_terms)}"

        return description

    def resolve_business_term(self, term: str) -> Optional[str]:
        """Convert business term to technical definition"""
        return self.business_glossary.get(term.lower())

    def get_eventstream_api_doc(self) -> str:
        """
        Documentation of OuthadEdge Eventstream API for LLM
        """
        return """
# OuthadEdge Eventstream API

## Creating Eventstream
```python
from outhad_edge import Eventstream
stream = Eventstream(df)  # df is pandas DataFrame with columns: user_id, event, timestamp
```

## Common Operations

### Funnel Analysis
```python
funnel = stream.funnel()
funnel.fit(stages=['homepage', 'product', 'cart', 'checkout', 'purchase'])
results = funnel.values  # Get conversion rates
```

### Cohort Analysis
```python
cohorts = stream.cohorts(
    cohort_start_unit='W',  # Weekly cohorts
    cohort_period=(1, 'W')  # 1-week retention periods
)
retention_data = cohorts.cohort_matrix
```

### Transition Graph
```python
graph = stream.transition_graph()
# Returns user flow visualization
```

### Data Access and Filtering
```python
# Get the underlying pandas DataFrame
df = stream.to_dataframe()

# Filter by event name
purchase_events = df[df['event'] == 'purchase']

# Count unique users who converted
converted_users = purchase_events['user_id'].nunique()
total_users = df['user_id'].nunique()

# Calculate conversion rate
conversion_rate = (converted_users / total_users) * 100
```

### User Segmentation
```python
clusters = stream.clusters()
X = clusters.extract_features(method='tfidf')
clusters.fit(method='kmeans', n_clusters=4, X=X)
segments = clusters.cluster_mapping
```

### Statistical Tests
```python
stats = stream.stattests()
result = stats.chi2_test(
    groups=['variant_a', 'variant_b'],
    event='purchase'
)
```

## Available Methods
- stream.describe() - Summary statistics
- stream.user_lifetime_hist() - User lifetime distribution
- stream.event_timestamp_hist() - Event timing patterns
- stream.to_dataframe() - Get underlying pandas DataFrame
"""

    def get_schema_statistics(self) -> Dict[str, Any]:
        """
        Get runtime statistics about the data.

        Returns:
            dict: Schema metadata with actual data statistics
        """
        df = self.df

        # Basic statistics
        total_events = len(df)
        unique_users = df[self.schema.user_id].nunique()

        # Date range
        timestamps = pd.to_datetime(df[self.schema.event_timestamp])
        date_range = f"{timestamps.min()} to {timestamps.max()}"

        # Event distribution
        event_counts = df[self.schema.event_name].value_counts()

        return {
            "user_id_type": str(df[self.schema.user_id].dtype),
            "event_type": str(df[self.schema.event_name].dtype),
            "timestamp_type": str(df[self.schema.event_timestamp].dtype),
            "total_events": total_events,
            "unique_users": unique_users,
            "date_range": date_range,
            "event_counts": event_counts.to_dict(),
            "columns": list(df.columns),
        }

    def get_event_catalog(self) -> List[Dict[str, Any]]:
        """
        Get catalog of all events with descriptions and frequencies.

        Returns:
            list: Event catalog with metadata
        """
        df = self.df
        event_col = self.schema.event_name

        event_stats = (
            df.groupby(event_col)
            .agg(
                {
                    self.schema.user_id: "nunique",
                    event_col: "count",
                }
            )
            .rename(
                columns={
                    self.schema.user_id: "unique_users",
                    event_col: "total_occurrences",
                }
            )
        )

        catalog = []
        for event_name in event_stats.index:
            catalog.append(
                {
                    "event": event_name,
                    "unique_users": int(event_stats.loc[event_name, "unique_users"]),
                    "total_occurrences": int(
                        event_stats.loc[event_name, "total_occurrences"]
                    ),
                    "percentage_of_users": round(
                        event_stats.loc[event_name, "unique_users"] / self.df[self.schema.user_id].nunique() * 100,
                        2,
                    ),
                }
            )

        return sorted(catalog, key=lambda x: x["total_occurrences"], reverse=True)

    def get_tools_description(self) -> str:
        """
        Get description of available Outhad_Edge analysis tools.

        Returns:
            str: Markdown-formatted description of tools
        """
        tools_desc = """
**Available Analysis Tools:**

1. **transition_graph()** - Visualize user flow between events as a network graph
   - Shows transition probabilities between events
   - Interactive visualization
   - Example: `stream.transition_graph()`

2. **funnel(stages=[...])** - Analyze conversion funnel
   - Calculate drop-off rates between stages
   - Supports stage grouping and timeouts
   - Example: `stream.funnel(stages=['view', 'cart', 'purchase'])`

3. **step_matrix(...)** - Step-by-step event distribution analysis
   - Shows event distribution at each path step
   - Can be centered on specific events
   - Example: `stream.step_matrix(max_steps=10)`

4. **cohorts(...)** - Retention cohort analysis
   - Group users by first activity date
   - Track retention over time
   - Example: `stream.cohorts(cohort_start_unit='M', cohort_period=(1, 'M'))`

5. **clusters** - Behavioral segmentation
   - Group users by similar behavior patterns
   - Uses TF-IDF, n-grams, or raw sequences
   - Example: `stream.clusters().fit(method='kmeans', n_clusters=5)`

6. **filter_events(events=[...])** - Filter to specific events
   - Keep only specified events
   - Example: `stream.filter_events(['purchase', 'signup'])`

7. **split_sessions(timeout=...)** - Split paths into sessions
   - Create session boundaries based on time gaps
   - Example: `stream.split_sessions(timeout=(30, 'm'))`

8. **to_dataframe()** - Get raw pandas DataFrame
   - Access underlying data
   - Use for custom pandas operations
"""
        return tools_desc

    def get_sample_data(self, n: int = 5) -> str:
        """
        Get sample rows from the eventstream as formatted string.

        Args:
            n: Number of sample rows

        Returns:
            str: Formatted sample data
        """
        sample = self.df.head(n)[
            [self.schema.user_id, self.schema.event_name, self.schema.event_timestamp]
        ]
        return sample.to_string(index=False)

    def get_full_context(self) -> str:
        """
        Get complete semantic context for LLM.

        Returns:
            str: Full context description combining all metadata
        """
        schema_stats = self.get_schema_statistics()
        event_catalog = self.get_event_catalog()
        tools_desc = self.get_tools_description()
        sample_data = self.get_sample_data()
        api_doc = self.get_eventstream_api_doc()

        # Format event list
        event_list_str = "\n".join(
            [
                f"  - {e['event']}: {e['total_occurrences']:,} occurrences, "
                f"{e['unique_users']:,} users ({e['percentage_of_users']:.1f}%)"
                for e in event_catalog[:15]  # Top 15 events
            ]
        )

        context = f"""
**Eventstream Overview:**
- Total Events: {schema_stats['total_events']:,}
- Unique Users: {schema_stats['unique_users']:,}
- Date Range: {schema_stats['date_range']}
- Columns: {', '.join(schema_stats['columns'])}

**Top Events:**
{event_list_str}

**Sample Data:**
{sample_data}

{tools_desc}

{api_doc}

**Data Access:**
- The eventstream is available as variable `stream`
- Access raw data with: `stream.to_dataframe()`
- Schema columns: user_id='{self.schema.user_id}', event='{self.schema.event_name}', timestamp='{self.schema.event_timestamp}'
"""
        return context

    def to_dict(self) -> Dict[str, Any]:
        """
        Export semantic layer to dictionary.

        Returns:
            dict: Complete semantic layer data
        """
        return {
            "metadata": {
                table: meta.dict() for table, meta in self.tables.items()
            },
            "glossary": self.business_glossary,
            "statistics": self.get_schema_statistics(),
            "events": self.get_event_catalog(),
            "api_documentation": self.get_eventstream_api_doc(),
            "full_context": self.get_full_context(),
        }
