"""Step definitions for sql_string_aware_coverage.feature.

These steps target specific uncovered lines in _sql_string_aware.py:
- Line 40: escaped single-quote handling (j += 2) in strip_sql_comments
- Lines 52-58: terminated dollar-quoted strings in strip_sql_comments
- Lines 60-62: unterminated dollar-quoted strings in strip_sql_comments
- Lines 70-71: nested block comment depth increment in strip_sql_comments
- Line 115: escaped single-quote handling (i += 2) in find_unquoted_semicolon
- Lines 125-130: dollar-quoted string handling in find_unquoted_semicolon
"""

from behave import given, then, when

from cleveragents.domain.models.acms._sql_string_aware import (
    find_unquoted_semicolon,
    strip_sql_comments,
)


# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("the sql_string_aware module is imported")
def step_module_imported(context):
    """Ensure the module is importable."""
    assert strip_sql_comments is not None
    assert find_unquoted_semicolon is not None


# ---------------------------------------------------------------------------
# Generic helpers for setting SQL content and calling functions
# ---------------------------------------------------------------------------
@given('SQL content "{sql}"')
def step_set_sql_content(context, sql):
    """Store literal SQL content on the context."""
    context.sql_content = sql


@when("I strip SQL comments")
def step_strip_comments(context):
    """Call strip_sql_comments on the stored content."""
    context.stripped_result = strip_sql_comments(context.sql_content)


@then('the stripped result should be "{expected}"')
def step_verify_stripped(context, expected):
    """Assert the stripped result matches the expected string."""
    assert context.stripped_result == expected, (
        f"Expected {expected!r}, got {context.stripped_result!r}"
    )


@when("I search for the first unquoted semicolon")
def step_find_semicolon(context):
    """Call find_unquoted_semicolon on the stored content."""
    context.semicolon_index = find_unquoted_semicolon(context.sql_content)


# ---------------------------------------------------------------------------
# strip_sql_comments — terminated anonymous dollar-quoted string (lines 52-58)
# ---------------------------------------------------------------------------
@given("SQL content with a terminated anonymous dollar-quoted string")
def step_sql_anon_dollar_string(context):
    """Create SQL with $$body$$ — anonymous dollar-quoted string."""
    context.sql_content = "SELECT $$hello world$$"


@then("the dollar-quoted body should be preserved intact")
def step_verify_anon_dollar_preserved(context):
    """Verify the anonymous dollar-quoted string is preserved."""
    assert context.stripped_result == "SELECT $$hello world$$", (
        f"Got {context.stripped_result!r}"
    )


# ---------------------------------------------------------------------------
# strip_sql_comments — terminated named dollar-quoted string (lines 52-58)
# ---------------------------------------------------------------------------
@given("SQL content with a terminated named dollar-quoted string")
def step_sql_named_dollar_string(context):
    """Create SQL with $tag$body$tag$ — named dollar-quoted string."""
    context.sql_content = "SELECT $fn$CREATE FUNCTION$fn$ AS val"


@then("the named dollar-quoted body should be preserved intact")
def step_verify_named_dollar_preserved(context):
    """Verify the named dollar-quoted string is preserved."""
    assert context.stripped_result == "SELECT $fn$CREATE FUNCTION$fn$ AS val", (
        f"Got {context.stripped_result!r}"
    )


# ---------------------------------------------------------------------------
# strip_sql_comments — unterminated dollar-quoted string (lines 60-62)
# ---------------------------------------------------------------------------
@given("SQL content with an unterminated dollar-quoted string")
def step_sql_unterminated_dollar_string(context):
    """Create SQL with a $$ that never closes."""
    context.sql_content = "SELECT $$this never ends"


@then("the rest of the content should be preserved as-is")
def step_verify_unterminated_dollar_preserved(context):
    """The entire remainder from $$ onwards should be kept."""
    assert context.stripped_result == "SELECT $$this never ends", (
        f"Got {context.stripped_result!r}"
    )


# ---------------------------------------------------------------------------
# find_unquoted_semicolon — escaped single quotes (line 115)
# ---------------------------------------------------------------------------
@then("the semicolon index should point to the one after the closing quote")
def step_verify_semicolon_after_quote(context):
    """The semicolon at the position right after the closing quote.

    Content: SELECT 'it''s;here'; DROP TABLE
    Indices: 0123456789...
    The string 'it''s;here' starts at index 7 and ends at index 19 (the closing ')
    The unquoted ; is at index 20.
    """
    sql = context.sql_content
    # The unquoted semicolon should be at the position of ';' after the closing quote
    idx = context.semicolon_index
    assert idx == sql.index(";", sql.rindex("'") + 1), (
        f"Expected semicolon after closing quote, got index {idx}"
    )


@then("the semicolon index should be {index:d}")
def step_verify_semicolon_index(context, index):
    """Assert the semicolon index matches the expected value."""
    assert context.semicolon_index == index, (
        f"Expected {index}, got {context.semicolon_index}"
    )


# ---------------------------------------------------------------------------
# find_unquoted_semicolon — terminated dollar-quoted strings (lines 125-129)
# ---------------------------------------------------------------------------
@given(
    "SQL content with dollar-quoted string containing a semicolon then real semicolon"
)
def step_sql_dollar_with_inner_semi(context):
    """Content: $$a;b$$;X — semicolon inside $$ should be skipped."""
    context.sql_content = "$$a;b$$;X"


@then("the semicolon should be found after the dollar-quoted string")
def step_verify_semi_after_dollar(context):
    """The unquoted semicolon is at index 7 (right after $$a;b$$)."""
    assert context.semicolon_index == 7, f"Expected 7, got {context.semicolon_index}"


@given(
    "SQL content with named dollar-quoted string containing a semicolon then real semicolon"
)
def step_sql_named_dollar_with_inner_semi(context):
    """Content: $t$a;b$t$;X — semicolon inside $t$ should be skipped."""
    context.sql_content = "$t$a;b$t$;X"


@then("the semicolon should be found after the named dollar-quoted string")
def step_verify_semi_after_named_dollar(context):
    """The unquoted semicolon is at index 9 (right after $t$a;b$t$)."""
    assert context.semicolon_index == 9, f"Expected 9, got {context.semicolon_index}"


# ---------------------------------------------------------------------------
# find_unquoted_semicolon — unterminated dollar-quoted (line 129 else branch)
# ---------------------------------------------------------------------------
@given("SQL content with an unterminated dollar-quoted string containing a semicolon")
def step_sql_unterminated_dollar_with_semi(context):
    """Content: $$a;b — the $$ never closes, so the ; inside is quoted."""
    context.sql_content = "$$a;b"


@then("ssacov the result should be -1 indicating no unquoted semicolon")
def step_verify_no_semicolon(context):
    """No unquoted semicolon exists."""
    assert context.semicolon_index == -1, f"Expected -1, got {context.semicolon_index}"


# ---------------------------------------------------------------------------
# strip_sql_comments — mix of dollar-quoted strings and comments
# ---------------------------------------------------------------------------
@given("SQL content mixing dollar-quoted strings and comments")
def step_sql_mixed(context):
    """Dollar-quoted string followed by a line comment and more SQL."""
    context.sql_content = "$$body$$ -- this is a comment\nSELECT 1"


@then("only the comments should be removed and dollar strings preserved")
def step_verify_mixed(context):
    """The dollar-quoted string stays, the comment is removed."""
    assert context.stripped_result == "$$body$$ \nSELECT 1", (
        f"Got {context.stripped_result!r}"
    )


# ---------------------------------------------------------------------------
# find_unquoted_semicolon with start offset
# ---------------------------------------------------------------------------
@when("I search for unquoted semicolon starting at offset {offset:d}")
def step_find_semicolon_with_offset(context, offset):
    """Call find_unquoted_semicolon with a start offset."""
    context.semicolon_index = find_unquoted_semicolon(context.sql_content, offset)


@then("the semicolon should be found at index {index:d}")
def step_verify_semicolon_at_index(context, index):
    """Assert the semicolon was found at the expected index."""
    assert context.semicolon_index == index, (
        f"Expected {index}, got {context.semicolon_index}"
    )
