"""Programmatic code generation helpers.

These functions produce code strings directly (without templates) and are
used both as standalone utilities and as building blocks for the Jinja2
template engine.
"""

from __future__ import annotations

import re
from typing import Any

from griot_core.scaffold.type_mapping import map_logical_type

# ── Naming helpers ───────────────────────────────────────────────────


def to_snake_case(name: str) -> str:
    """Convert a string to snake_case."""
    s = re.sub(r"[\s\-]+", "_", name)
    s = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", s)
    s = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s)
    return s.lower().strip("_")


def to_pascal_case(name: str) -> str:
    """Convert a string to PascalCase."""
    parts = re.split(r"[\s_\-]+", name)
    return "".join(word.capitalize() for word in parts if word)


# ── DDL generation ───────────────────────────────────────────────────


def generate_ddl(
    contract: Any,
    target: str,
    schema_name: str | None = None,
) -> str:
    """Generate CREATE TABLE DDL statements from all schemas in a contract.

    Args:
        contract: A ``Contract`` instance (griot-core).
        target: Target warehouse (e.g. ``"databricks"``).
        schema_name: Optional database/schema prefix.

    Returns:
        SQL DDL string covering every schema.
    """
    schemas = contract.schemas
    if not schemas:
        return "-- No schemas found in this contract.\n"

    header = (
        f"-- Auto-generated by Griot scaffold\n-- Contract: {contract.id}\n-- Target: {target}\n"
    )

    ddl_parts: list[str] = [header]

    for schema in schemas:
        fields_dict = schema.fields
        if not fields_dict:
            ddl_parts.append(f"\n-- Schema '{schema.name}' has no fields.\n")
            continue

        raw_name = schema.physical_name or to_snake_case(schema.name)
        if schema_name:
            leaf = raw_name.rsplit(".", 1)[-1]
            table_name = f"{schema_name}.{leaf}"
        else:
            table_name = raw_name

        lines: list[str] = [f"\nCREATE TABLE IF NOT EXISTS {table_name} ("]
        col_defs: list[str] = []
        pk_cols: list[str] = []

        for field_info in fields_dict.values():
            sql_type = map_logical_type(field_info.logical_type, target)
            parts = [f"    {field_info.name}  {sql_type}"]

            if not field_info.nullable:
                parts.append("NOT NULL")

            col_defs.append(" ".join(parts))

            if field_info.primary_key:
                pk_cols.append(field_info.name)

        lines.append(",\n".join(col_defs))

        if pk_cols:
            lines.append(f"    , PRIMARY KEY ({', '.join(pk_cols)})")

        # Foreign key constraints
        for field_info in fields_dict.values():
            rels = field_info.relationships if hasattr(field_info, "relationships") else []
            for rel in rels:
                if rel.get("type") == "foreignKey":
                    ref_table = to_snake_case(rel.get("toSchema", ""))
                    ref_col = rel.get("to", "")
                    if ref_table and ref_col:
                        if target in ("databricks", "snowflake"):
                            lines.append(
                                f"    , FOREIGN KEY ({field_info.name}) REFERENCES {ref_table}({ref_col}) NOT ENFORCED"
                            )
                        else:
                            lines.append(f"    -- FK: {field_info.name} -> {ref_table}({ref_col})")

        if target == "databricks":
            lines.append(") USING DELTA;")
        else:
            lines.append(");")

        ddl_parts.append("\n".join(lines) + "\n")

    return "\n".join(ddl_parts)


# ── Test stub generation ─────────────────────────────────────────────


def generate_contract_tests(contract: Any) -> str:
    """Generate pytest test stubs from contract quality rules and constraints.

    Generates test classes for every schema in the contract.

    Args:
        contract: A ``Contract`` instance.

    Returns:
        Python source code string.
    """
    schemas = contract.schemas
    if not schemas:
        return "# No schemas found in this contract.\n"

    lines: list[str] = [
        f'"""Auto-generated tests for {contract.id} contract."""',
        "from __future__ import annotations",
        "",
        "import pytest",
        "",
    ]

    for schema in schemas:
        fields_dict = schema.fields
        class_name = to_pascal_case(schema.name)

        lines.append("")
        lines.append(f"# ── {schema.name} " + "─" * 50)

        # Generate tests for NOT NULL fields
        required_fields = [f for f in fields_dict.values() if not f.nullable or f.required]
        if required_fields:
            lines.append("")
            lines.append(f"class Test{class_name}NotNull:")
            lines.append(f'    """Verify required fields are not null for {schema.name}."""')
            lines.append("")
            for field_info in required_fields:
                fname = to_snake_case(field_info.name)
                lines.append(f"    def test_{fname}_not_null(self, df):")
                lines.append(f'        """Field \'{field_info.name}\' must not be null."""')
                lines.append(f'        assert df["{field_info.name}"].notnull().all()')
                lines.append("")

        # Generate tests for UNIQUE fields
        unique_fields = [f for f in fields_dict.values() if f.unique or f.primary_key]
        if unique_fields:
            lines.append(f"class Test{class_name}Uniqueness:")
            lines.append(f'    """Verify unique constraints for {schema.name}."""')
            lines.append("")
            for field_info in unique_fields:
                fname = to_snake_case(field_info.name)
                lines.append(f"    def test_{fname}_unique(self, df):")
                lines.append(f'        """Field \'{field_info.name}\' must have unique values."""')
                lines.append(f'        assert df["{field_info.name}"].is_unique')
                lines.append("")

        # Generate tests for FOREIGN KEY integrity
        fk_fields = [
            f for f in fields_dict.values() if hasattr(f, "relationships") and f.relationships
        ]
        if fk_fields:
            lines.append(f"class Test{class_name}ForeignKeys:")
            lines.append(f'    """Verify foreign key integrity for {schema.name}."""')
            lines.append("")
            for field_info in fk_fields:
                for rel in field_info.relationships:
                    if rel.get("type") == "foreignKey":
                        fname = to_snake_case(field_info.name)
                        ref_table = rel.get("toSchema", "unknown")
                        ref_col = rel.get("to", "unknown")
                        ref_var = to_snake_case(ref_table)
                        lines.append(f"    def test_{fname}_fk_valid(self, df, {ref_var}_df):")
                        lines.append(
                            f'        """FK: {field_info.name} -> {ref_table}.{ref_col}"""'
                        )
                        lines.append(
                            f'        assert df["{field_info.name}"].isin({ref_var}_df["{ref_col}"]).all()'
                        )
                        lines.append("")

        # Generate tests from schema-level quality rules
        quality_rules = schema.quality or []
        if quality_rules:
            lines.append(f"class Test{class_name}Quality:")
            lines.append(f'    """Quality rules from contract for {schema.name}."""')
            lines.append("")
            for rule in quality_rules:
                rule_id = rule.get("id", rule.get("metric", "rule"))
                safe_id = to_snake_case(str(rule_id))
                lines.append(f"    def test_{safe_id}(self, df):")
                lines.append(f'        """Quality rule: {rule_id}."""')
                lines.append("        # TODO: implement quality check")
                lines.append("        pass")
                lines.append("")

    return "\n".join(lines) + "\n"
