"""Cypher → SQL translation for Apache AGE.

Implements the two-step execution pattern:
1. age_prepare_cypher(graph, cypher) — registers the query
2. SELECT * FROM cypher(NULL, NULL) AS (col1 agtype, ...) — executes it
"""

from __future__ import annotations

import re
from typing import Any

from psycopg import sql
from psycopg.client_cursor import ClientCursor

from piggie._cypher.parser import extract_return_columns

_VALID_IDENTIFIER = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
_MAX_IDENT_LEN = 63


def _validate_identifier(name: str, context: str = "identifier") -> None:
    if not name or len(name) > _MAX_IDENT_LEN or not _VALID_IDENTIFIER.match(name):
        raise ValueError(
            f"Invalid {context}: {name!r}. Must match [A-Za-z_][A-Za-z0-9_]* "
            f"and be at most {_MAX_IDENT_LEN} chars."
        )


def build_cypher_sql(columns: list[str]) -> str:
    """Build the SELECT wrapper for a Cypher query.

    Returns SQL like: SELECT * FROM cypher(NULL,NULL) AS (col1 agtype, col2 agtype);
    """
    col_specs = []
    for col in columns:
        _validate_identifier(col, "column name")
        col_specs.append(f"{col} agtype")
    return f"SELECT * FROM cypher(NULL,NULL) AS ({','.join(col_specs)});"


def execute_cypher(
    conn: Any,
    graph_name: str,
    cypher_stmt: str,
    params: tuple[Any, ...] | None = None,
    columns: list[str] | None = None,
) -> tuple[list[tuple[Any, ...]], list[str]]:
    """Execute a Cypher query and return (rows, column_names).

    Uses the two-step AGE execution pattern:
    1. Call age_prepare_cypher to register the graph + query
    2. Execute SELECT * FROM cypher(NULL,NULL) AS (...)

    Args:
        conn: A psycopg connection (must use ClientCursor factory).
        graph_name: The AGE graph to query.
        cypher_stmt: The Cypher query string.
        params: Optional parameters for mogrify substitution (%s placeholders).
        columns: Explicit column names. If None, extracted from RETURN clause.

    Returns:
        Tuple of (rows, column_names) where rows is a list of tuples.
    """
    # Clean whitespace from Cypher for mogrify
    cleaned = cypher_stmt.replace("\n", " ").replace("\t", " ")

    # Mogrify parameters into the Cypher string
    cursor: ClientCursor = conn.cursor()
    if params:
        mogrified = str(cursor.mogrify(cleaned, params)).strip()
    else:
        mogrified = cleaned.strip()

    # Auto-extract columns from RETURN clause if not provided
    if columns is None:
        columns = extract_return_columns(mogrified)

    # Step 1: Prepare the Cypher query
    prepare_stmt = sql.SQL(
        "SELECT * FROM age_prepare_cypher({graph},{cypher})"
    ).format(
        graph=sql.Literal(graph_name),
        cypher=sql.Literal(mogrified),
    )

    cursor = conn.cursor()
    cursor.execute(prepare_stmt)

    # Step 2: Execute the prepared query
    select_stmt = build_cypher_sql(columns)
    cursor = conn.cursor()
    cursor.execute(select_stmt)

    rows = cursor.fetchall()
    return rows, columns
