#!/usr/bin/env python3
"""
Dump transpiled SQL for a specific test query.

This script is used by Makefile targets to quickly inspect the SQL
generated by the transpiler for any test query.

Usage:
    python scripts/dump_query_sql.py <test_id> <test_name>
    python scripts/dump_query_sql.py 01 simple_node_lookup
    python scripts/dump_query_sql.py 02 node_with_property_filter

Options:
    --diff    Show diff against expected golden file
    --save    Save actual output to tests/output/actual/
    --cypher  Specify custom Cypher query instead of default
"""

import argparse
import sys
from pathlib import Path

# Add project to path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
sys.path.insert(0, str(Path(__file__).parent.parent))

from gsql2rsql import OpenCypherParser, LogicalPlan, SQLRenderer
from gsql2rsql.common.schema import (
    SimpleGraphSchemaProvider,
    NodeSchema,
    EdgeSchema,
    EntityProperty,
)
from gsql2rsql.renderer.schema_provider import (
    SimpleSQLSchemaProvider,
    SQLTableDescriptor,
)

from tests.utils.sql_test_utils import (
    normalize_sql,
    load_expected_sql,
    EXPECTED_DIR,
    ACTUAL_DIR,
    DIFF_DIR,
    ensure_output_dirs,
)

# Default test queries by ID
DEFAULT_QUERIES: dict[str, str] = {
    "01": "MATCH (p:Person) RETURN p",
    "02": "MATCH (p:Person) WHERE p.name = 'Alice' RETURN p",
    "03": "MATCH (p:Person) RETURN p.name AS personName, p.id AS personId",
    "06": "MATCH (p:Person)-[:KNOWS]->(f:Person) RETURN p.name, f.name",
    "11": "MATCH (p:Person)-[:LIVES_IN]->(c:City) RETURN c.name AS city, COUNT(p) AS population",
    "12": "MATCH (p:Person)-[:LIVES_IN]->(c:City) RETURN c.name AS city, COUNT(p) AS population ORDER BY population DESC",
    "14": "MATCH (c:City)<-[:LIVES_IN]-(p:Person) RETURN c.name AS city, COLLECT(p.name) AS residents",
    "15": "MATCH (p:Person)-[:KNOWS]->(f:Person) RETURN DISTINCT f.name",
    "17": "MATCH (p:Person) RETURN p.name, CASE WHEN p.age < 18 THEN 'minor' WHEN p.age >= 65 THEN 'senior' ELSE 'adult' END AS ageGroup",
    "18": "MATCH (p:Person) WHERE EXISTS { (p)-[:ACTED_IN]->(:Movie) } RETURN p.name",
    "19": "MATCH (p:Person) RETURN p.name AS name UNION MATCH (c:City) RETURN c.name AS name",
    "20": "MATCH (p:Person) RETURN COALESCE(p.nickname, p.name) AS displayName",
    "21": "MATCH (p:Person)-[:KNOWS*0..2]->(f:Person) RETURN DISTINCT f.name",
}


def create_default_schema() -> tuple[
    SimpleGraphSchemaProvider, SimpleSQLSchemaProvider
]:
    """Create default Person/Movie schema for testing."""
    graph_schema = SimpleGraphSchemaProvider()

    # Nodes
    graph_schema.add_node(
        NodeSchema(
            name="Person",
            properties=[
                EntityProperty("id", int),
                EntityProperty("name", str),
                EntityProperty("age", int),
                EntityProperty("nickname", str),
            ],
            node_id_property=EntityProperty("id", int),
        )
    )
    graph_schema.add_node(
        NodeSchema(
            name="Movie",
            properties=[
                EntityProperty("id", int),
                EntityProperty("title", str),
            ],
            node_id_property=EntityProperty("id", int),
        )
    )
    graph_schema.add_node(
        NodeSchema(
            name="City",
            properties=[
                EntityProperty("id", int),
                EntityProperty("name", str),
            ],
            node_id_property=EntityProperty("id", int),
        )
    )

    # Edges
    graph_schema.add_edge(
        EdgeSchema(
            name="KNOWS",
            source_node_id="Person",
            sink_node_id="Person",
            source_id_property=EntityProperty("source_id", int),
            sink_id_property=EntityProperty("target_id", int),
        )
    )
    graph_schema.add_edge(
        EdgeSchema(
            name="ACTED_IN",
            source_node_id="Person",
            sink_node_id="Movie",
            source_id_property=EntityProperty("source_id", int),
            sink_id_property=EntityProperty("target_id", int),
        )
    )
    graph_schema.add_edge(
        EdgeSchema(
            name="LIVES_IN",
            source_node_id="Person",
            sink_node_id="City",
            source_id_property=EntityProperty("source_id", int),
            sink_id_property=EntityProperty("target_id", int),
        )
    )

    # SQL schema
    sql_schema = SimpleSQLSchemaProvider()

    sql_schema.add_node(
        NodeSchema(
            name="Person",
            properties=[
                EntityProperty("id", int),
                EntityProperty("name", str),
                EntityProperty("age", int),
                EntityProperty("nickname", str),
            ],
            node_id_property=EntityProperty("id", int),
        ),
        SQLTableDescriptor(
            table_name="dbo.Person",
            node_id_columns=["id"],
        ),
    )
    sql_schema.add_node(
        NodeSchema(
            name="Movie",
            properties=[
                EntityProperty("id", int),
                EntityProperty("title", str),
            ],
            node_id_property=EntityProperty("id", int),
        ),
        SQLTableDescriptor(
            table_name="dbo.Movie",
            node_id_columns=["id"],
        ),
    )
    sql_schema.add_edge(
        EdgeSchema(
            name="KNOWS",
            source_node_id="Person",
            sink_node_id="Person",
            source_id_property=EntityProperty("source_id", int),
            sink_id_property=EntityProperty("target_id", int),
        ),
        SQLTableDescriptor(
            entity_id="Person@KNOWS@Person",
            table_name="dbo.Knows",
            node_id_columns=["person1_id", "person2_id"],
        ),
    )
    sql_schema.add_edge(
        EdgeSchema(
            name="ACTED_IN",
            source_node_id="Person",
            sink_node_id="Movie",
            source_id_property=EntityProperty("source_id", int),
            sink_id_property=EntityProperty("target_id", int),
        ),
        SQLTableDescriptor(
            entity_id="Person@ACTED_IN@Movie",
            table_name="dbo.ActedIn",
            node_id_columns=["person_id", "movie_id"],
        ),
    )
    sql_schema.add_node(
        NodeSchema(
            name="City",
            properties=[
                EntityProperty("id", int),
                EntityProperty("name", str),
            ],
            node_id_property=EntityProperty("id", int),
        ),
        SQLTableDescriptor(
            table_name="graph.City",
            node_id_columns=["id"],
        ),
    )
    sql_schema.add_edge(
        EdgeSchema(
            name="LIVES_IN",
            source_node_id="Person",
            sink_node_id="City",
            source_id_property=EntityProperty("source_id", int),
            sink_id_property=EntityProperty("target_id", int),
        ),
        SQLTableDescriptor(
            table_name="graph.LivesIn",
        ),
    )

    return graph_schema, sql_schema


def transpile(
    cypher: str,
    graph_schema: SimpleGraphSchemaProvider,
    sql_schema: SimpleSQLSchemaProvider,
) -> str:
    """Transpile a Cypher query to SQL."""
    parser = OpenCypherParser()
    ast = parser.parse(cypher)
    plan = LogicalPlan.process_query_tree(ast, graph_schema)
    plan.resolve(original_query=cypher)
    renderer = SQLRenderer(db_schema_provider=sql_schema)
    return renderer.render_plan(plan)


def main() -> None:
    """Main entry point."""
    parser = argparse.ArgumentParser(
        description="Dump transpiled SQL for a test query"
    )
    parser.add_argument("test_id", help="Test ID (e.g., 01, 02)")
    parser.add_argument("test_name", help="Test name (e.g., simple_node_lookup)")
    parser.add_argument(
        "--diff",
        action="store_true",
        help="Show diff against expected golden file",
    )
    parser.add_argument(
        "--save",
        action="store_true",
        help="Save actual output to tests/output/actual/",
    )
    parser.add_argument(
        "--cypher",
        type=str,
        default=None,
        help="Custom Cypher query (overrides default)",
    )

    args = parser.parse_args()

    # Get Cypher query
    if args.cypher:
        cypher = args.cypher
    elif args.test_id in DEFAULT_QUERIES:
        cypher = DEFAULT_QUERIES[args.test_id]
    else:
        print(f"Error: No default query for test ID {args.test_id}")
        print("Use --cypher to specify a custom query")
        print(f"Available defaults: {list(DEFAULT_QUERIES.keys())}")
        sys.exit(1)

    # Create schemas and transpile
    graph_schema, sql_schema = create_default_schema()
    actual_sql = transpile(cypher, graph_schema, sql_schema)

    # Print header
    print(f"{'=' * 60}")
    print(f"Test: {args.test_id}_{args.test_name}")
    print(f"{'=' * 60}")
    print()
    print("Cypher Query:")
    print(f"  {cypher}")
    print()
    print("Generated SQL:")
    print("-" * 60)
    print(actual_sql)
    print("-" * 60)

    # Save if requested
    if args.save:
        ensure_output_dirs()
        # Save to expected directory as the golden file
        expected_path = EXPECTED_DIR / f"{args.test_id}_{args.test_name}.sql"
        expected_path.write_text(normalize_sql(actual_sql))
        print(f"\nSaved golden file to: {expected_path}")

    # Show diff if requested
    if args.diff:
        expected_sql = load_expected_sql(args.test_id, args.test_name)
        if expected_sql is None:
            print(f"\nNo golden file found at:")
            print(f"  {EXPECTED_DIR}/{args.test_id}_{args.test_name}.sql")
        else:
            import difflib

            exp_norm = normalize_sql(expected_sql)
            act_norm = normalize_sql(actual_sql)

            if exp_norm == act_norm:
                print("\n✓ SQL matches expected golden file")
            else:
                print("\n✗ SQL differs from expected:")
                print("-" * 60)
                diff = difflib.unified_diff(
                    exp_norm.splitlines(keepends=True),
                    act_norm.splitlines(keepends=True),
                    fromfile="expected",
                    tofile="actual",
                )
                print("".join(diff))


if __name__ == "__main__":
    main()
