# src/kontra/scout/suggest.py
"""
Generate suggested validation rules based on profile analysis.
"""

from __future__ import annotations

from typing import Any, Dict, List

from .patterns import get_pattern_regex
from .types import DatasetProfile, ColumnProfile


def generate_rules(profile: DatasetProfile) -> List[Dict[str, Any]]:
    """
    Generate suggested validation rules based on profile.

    Returns a list of rule dictionaries in Kontra contract format.
    """
    rules: List[Dict[str, Any]] = []

    for col in profile.columns:
        col_rules = _suggest_column_rules(col)
        rules.extend(col_rules)

    return rules


def generate_rules_yaml(profile: DatasetProfile) -> str:
    """
    Generate YAML-formatted contract with suggested rules.

    Returns a complete contract YAML that can be used directly with `kontra validate`.
    """
    rules = generate_rules(profile)

    if not rules:
        return "# No rules suggested - dataset may be empty or all columns have high null rates\nrules: []"

    lines = [
        f"# Auto-generated contract from Kontra Scout",
        f"# Source: {profile.source_uri}",
        f"# Rows analyzed: {profile.row_count:,}",
        f"# Generated by Kontra Scout v{profile.engine_version}",
        "",
        "name: suggested_contract",
        f"description: Auto-generated from Scout profile",
        "",
        f'datasource: "{profile.source_uri}"',
        "",
        "rules:",
    ]

    # Add min_rows suggestion (90% of current count)
    min_threshold = int(profile.row_count * 0.9)
    if min_threshold > 0:
        lines.append(f"  # Dataset: expecting at least {min_threshold:,} rows (90% of current)")
        lines.append("  - name: min_rows")
        lines.append(f"    params: {{ threshold: {min_threshold} }}")
        lines.append("")

    for rule in rules:
        name = rule["name"]
        params = rule.get("params", {})
        comment = rule.get("_comment", "")

        if comment:
            lines.append(f"  # {comment}")

        if params:
            # Format params inline for simple cases, multi-line for complex
            if len(params) <= 2 and all(
                not isinstance(v, (list, dict)) or (isinstance(v, list) and len(v) <= 5)
                for v in params.values()
            ):
                params_str = ", ".join(_format_param(k, v) for k, v in params.items())
                lines.append(f"  - name: {name}")
                lines.append(f"    params: {{ {params_str} }}")
            else:
                lines.append(f"  - name: {name}")
                lines.append("    params:")
                for k, v in params.items():
                    lines.append(f"      {k}: {_format_value(v)}")
        else:
            lines.append(f"  - name: {name}")

        lines.append("")

    return "\n".join(lines)


def _suggest_column_rules(col: ColumnProfile) -> List[Dict[str, Any]]:
    """Generate rules for a single column based on its profile."""
    rules: List[Dict[str, Any]] = []

    # 1. not_null: Suggest if column has 0% nulls
    if col.null_rate == 0 and col.row_count > 0:
        rules.append({
            "name": "not_null",
            "params": {"column": col.name},
            "_comment": f"{col.name}: 100% non-null",
        })

    # 2. unique: Suggest only for columns that look like identifiers
    is_identifier = col.semantic_type == "identifier"
    name_hints = any(hint in col.name.lower() for hint in ["_id", "id", "key", "uuid", "guid", "pk", "code"])
    if col.uniqueness_ratio >= 0.999 and col.null_rate == 0 and (is_identifier or name_hints):
        rules.append({
            "name": "unique",
            "params": {"column": col.name},
            "_comment": f"{col.name}: 100% unique (primary key candidate)",
        })

    # 3. allowed_values: Suggest for low-cardinality columns with known values
    if col.is_low_cardinality and col.values and len(col.values) <= 20:
        rules.append({
            "name": "allowed_values",
            "params": {
                "column": col.name,
                "values": col.values,
            },
            "_comment": f"{col.name}: low cardinality ({len(col.values)} values)",
        })

    # 4. dtype: Suggest type validation
    if col.dtype in ("int", "float", "bool", "date", "datetime", "string"):
        # Map to Kontra/Polars type names
        type_map = {
            "int": "int64",
            "float": "float64",
            "bool": "bool",
            "date": "date",
            "datetime": "datetime",
            "string": "utf8",
        }
        kontra_type = type_map.get(col.dtype, col.dtype)
        rules.append({
            "name": "dtype",
            "params": {
                "column": col.name,
                "type": kontra_type,
            },
            "_comment": f"{col.name}: detected type {col.dtype_raw}",
        })

    # 5. regex: Suggest for detected patterns
    for pattern in col.detected_patterns:
        regex = get_pattern_regex(pattern)
        if regex:
            rules.append({
                "name": "regex",
                "params": {
                    "column": col.name,
                    "pattern": regex,
                },
                "_comment": f"{col.name}: detected {pattern} pattern",
            })

    # 6. freshness: Suggest for timestamp columns that look like update times
    if col.dtype in ("datetime", "timestamp") or col.temporal is not None:
        # Check if column name suggests it's an update/modified timestamp
        name_lower = col.name.lower()
        freshness_hints = ["updated", "modified", "timestamp", "created_at", "updated_at", "last_"]
        if any(hint in name_lower for hint in freshness_hints):
            rules.append({
                "name": "freshness",
                "params": {
                    "column": col.name,
                    "max_age": "7d",
                },
                "_comment": f"{col.name}: timestamp column, adjust max_age as needed",
            })

    return rules


def _format_param(key: str, value: Any) -> str:
    """Format a single parameter for inline YAML."""
    return f"{key}: {_format_value(value)}"


def _format_value(value: Any) -> str:
    """Format a value for YAML output."""
    if isinstance(value, str):
        # Escape quotes and use quotes if needed
        if any(c in value for c in ['"', "'", ":", "{", "}", "[", "]", ",", "\n"]):
            escaped = value.replace("\\", "\\\\").replace('"', '\\"')
            return f'"{escaped}"'
        return f'"{value}"'
    elif isinstance(value, bool):
        return "true" if value else "false"
    elif isinstance(value, (int, float)):
        return str(value)
    elif isinstance(value, list):
        items = ", ".join(_format_value(v) for v in value)
        return f"[{items}]"
    elif value is None:
        return "null"
    else:
        return str(value)
