"""
Deep behavioral tests for query building patterns.
Tests SQL query construction, filter building,
sort ordering, and pagination query logic.
"""

from datetime import datetime, timezone, timedelta


# --- Basic query builder patterns ---


class TestBasicQueryBuilder:
    """Tests for basic query builder patterns."""

    def _build_select(self, table, columns=None):
        """Build a SELECT query."""
        cols = ", ".join(columns) if columns else "*"
        return f"SELECT {cols} FROM {table}"

    def _add_where(self, query, conditions):
        """Add WHERE clause to query."""
        if not conditions:
            return query
        where_str = " AND ".join(conditions)
        return f"{query} WHERE {where_str}"

    def test_select_all(self):
        query = self._build_select("users")
        assert query == "SELECT * FROM users"

    def test_select_columns(self):
        query = self._build_select("users", ["id", "name"])
        assert query == "SELECT id, name FROM users"

    def test_add_single_where(self):
        query = self._build_select("users")
        query = self._add_where(query, ["active = 1"])
        assert "WHERE active = 1" in query

    def test_add_multiple_where(self):
        query = self._build_select("users")
        query = self._add_where(query, ["active = 1", "role = 'admin'"])
        assert "WHERE active = 1 AND role = 'admin'" in query


class TestParameterizedQuery:
    """Tests for parameterized query building."""

    def _build_parameterized(self, template, params):
        """Build parameterized query (for display only)."""
        return {"query": template, "params": params}

    def _count_placeholders(self, query):
        """Count placeholders in query."""
        return query.count("?")

    def test_single_param(self):
        result = self._build_parameterized(
            "SELECT * FROM users WHERE id = ?", [123]
        )
        assert result["query"] == "SELECT * FROM users WHERE id = ?"
        assert result["params"] == [123]

    def test_multiple_params(self):
        result = self._build_parameterized(
            "SELECT * FROM users WHERE status = ? AND role = ?",
            ["active", "admin"],
        )
        assert len(result["params"]) == 2

    def test_count_placeholders(self):
        query = "SELECT * FROM t WHERE a = ? AND b = ? AND c = ?"
        assert self._count_placeholders(query) == 3


# --- Filter building patterns ---


class TestFilterBuilder:
    """Tests for filter building patterns."""

    def _build_filter(self, field, operator, value):
        """Build a single filter condition."""
        if operator == "eq":
            return f"{field} = ?", [value]
        if operator == "ne":
            return f"{field} != ?", [value]
        if operator == "gt":
            return f"{field} > ?", [value]
        if operator == "gte":
            return f"{field} >= ?", [value]
        if operator == "lt":
            return f"{field} < ?", [value]
        if operator == "lte":
            return f"{field} <= ?", [value]
        if operator == "like":
            return f"{field} LIKE ?", [f"%{value}%"]
        if operator == "in":
            placeholders = ", ".join(["?"] * len(value))
            return f"{field} IN ({placeholders})", value
        return None, []

    def test_equals_filter(self):
        condition, params = self._build_filter("status", "eq", "active")
        assert condition == "status = ?"
        assert params == ["active"]

    def test_greater_than_filter(self):
        condition, params = self._build_filter("score", "gt", 50)
        assert condition == "score > ?"
        assert params == [50]

    def test_like_filter(self):
        condition, params = self._build_filter("name", "like", "john")
        assert condition == "name LIKE ?"
        assert params == ["%john%"]

    def test_in_filter(self):
        condition, params = self._build_filter("id", "in", [1, 2, 3])
        assert "IN (?, ?, ?)" in condition
        assert params == [1, 2, 3]


class TestDateRangeFilter:
    """Tests for date range filter building."""

    def _build_date_range(self, field, start=None, end=None):
        """Build date range filter."""
        conditions = []
        params = []
        if start:
            conditions.append(f"{field} >= ?")
            params.append(start)
        if end:
            conditions.append(f"{field} <= ?")
            params.append(end)
        return conditions, params

    def _build_relative_date(self, field, days_ago):
        """Build filter for relative date."""
        cutoff = datetime.now(timezone.utc) - timedelta(days=days_ago)
        return f"{field} >= ?", [cutoff]

    def test_start_only(self):
        start = datetime(2025, 1, 1, tzinfo=timezone.utc)
        conditions, params = self._build_date_range("created_at", start=start)
        assert len(conditions) == 1
        assert "created_at >= ?" in conditions[0]

    def test_end_only(self):
        end = datetime(2025, 12, 31, tzinfo=timezone.utc)
        conditions, params = self._build_date_range("created_at", end=end)
        assert len(conditions) == 1
        assert "created_at <= ?" in conditions[0]

    def test_both_bounds(self):
        start = datetime(2025, 1, 1, tzinfo=timezone.utc)
        end = datetime(2025, 12, 31, tzinfo=timezone.utc)
        conditions, params = self._build_date_range(
            "created_at", start=start, end=end
        )
        assert len(conditions) == 2
        assert len(params) == 2


# --- Sort building patterns ---


class TestSortBuilder:
    """Tests for sort clause building."""

    def _build_order_by(self, sort_fields):
        """Build ORDER BY clause."""
        if not sort_fields:
            return ""
        parts = []
        for field in sort_fields:
            if field.startswith("-"):
                parts.append(f"{field[1:]} DESC")
            else:
                parts.append(f"{field} ASC")
        return "ORDER BY " + ", ".join(parts)

    def _parse_sort_string(self, sort_str):
        """Parse sort string into fields."""
        if not sort_str:
            return []
        return [s.strip() for s in sort_str.split(",")]

    def test_single_asc(self):
        order = self._build_order_by(["name"])
        assert order == "ORDER BY name ASC"

    def test_single_desc(self):
        order = self._build_order_by(["-created_at"])
        assert order == "ORDER BY created_at DESC"

    def test_multiple_sort(self):
        order = self._build_order_by(["status", "-created_at"])
        assert "status ASC" in order
        assert "created_at DESC" in order

    def test_empty_sort(self):
        order = self._build_order_by([])
        assert order == ""

    def test_parse_sort_string(self):
        fields = self._parse_sort_string("-created_at, name")
        assert fields == ["-created_at", "name"]


# --- Pagination patterns ---


class TestPaginationQuery:
    """Tests for pagination query building."""

    def _build_pagination(self, page, page_size):
        """Build LIMIT/OFFSET clause."""
        offset = (page - 1) * page_size
        return f"LIMIT {page_size} OFFSET {offset}"

    def _calculate_total_pages(self, total_count, page_size):
        """Calculate total pages."""
        if page_size <= 0:
            return 0
        return (total_count + page_size - 1) // page_size

    def test_first_page(self):
        clause = self._build_pagination(page=1, page_size=10)
        assert clause == "LIMIT 10 OFFSET 0"

    def test_second_page(self):
        clause = self._build_pagination(page=2, page_size=10)
        assert clause == "LIMIT 10 OFFSET 10"

    def test_total_pages_exact(self):
        pages = self._calculate_total_pages(100, 10)
        assert pages == 10

    def test_total_pages_remainder(self):
        pages = self._calculate_total_pages(101, 10)
        assert pages == 11


class TestCursorPagination:
    """Tests for cursor-based pagination."""

    def _encode_cursor(self, sort_value, item_id):
        """Encode cursor from sort value and ID."""
        import base64

        cursor_str = f"{sort_value}:{item_id}"
        return base64.b64encode(cursor_str.encode()).decode()

    def _decode_cursor(self, cursor):
        """Decode cursor to sort value and ID."""
        import base64

        try:
            decoded = base64.b64decode(cursor.encode()).decode()
            sort_value, item_id = decoded.rsplit(":", 1)
            return sort_value, item_id
        except Exception:
            return None, None

    def _build_cursor_condition(self, cursor, sort_field, direction="asc"):
        """Build WHERE condition for cursor pagination."""
        sort_value, item_id = self._decode_cursor(cursor)
        if sort_value is None:
            return None, []
        op = ">" if direction == "asc" else "<"
        condition = f"({sort_field} {op} ? OR ({sort_field} = ? AND id {op} ?))"
        return condition, [sort_value, sort_value, item_id]

    def test_encode_decode_roundtrip(self):
        cursor = self._encode_cursor("2025-01-01", "123")
        sort_value, item_id = self._decode_cursor(cursor)
        assert sort_value == "2025-01-01"
        assert item_id == "123"

    def test_build_cursor_condition(self):
        cursor = self._encode_cursor("value", "id123")
        condition, params = self._build_cursor_condition(cursor, "created_at")
        assert condition is not None
        assert len(params) == 3


# --- Join building patterns ---


class TestJoinBuilder:
    """Tests for JOIN clause building."""

    def _build_join(self, join_type, table, condition):
        """Build a JOIN clause."""
        return f"{join_type} JOIN {table} ON {condition}"

    def _build_inner_join(self, table, left_col, right_col):
        """Build INNER JOIN."""
        return f"INNER JOIN {table} ON {left_col} = {right_col}"

    def _build_left_join(self, table, left_col, right_col):
        """Build LEFT JOIN."""
        return f"LEFT JOIN {table} ON {left_col} = {right_col}"

    def test_inner_join(self):
        join = self._build_inner_join("orders", "users.id", "orders.user_id")
        assert "INNER JOIN orders ON users.id = orders.user_id" == join

    def test_left_join(self):
        join = self._build_left_join("profiles", "users.id", "profiles.user_id")
        assert "LEFT JOIN profiles ON users.id = profiles.user_id" == join

    def test_custom_join(self):
        join = self._build_join("RIGHT", "items", "orders.id = items.order_id")
        assert "RIGHT JOIN items ON orders.id = items.order_id" == join


# --- Aggregate query patterns ---


class TestAggregateQuery:
    """Tests for aggregate query building."""

    def _build_count(self, table, column="*"):
        """Build COUNT query."""
        return f"SELECT COUNT({column}) FROM {table}"

    def _build_aggregate(self, table, function, column):
        """Build aggregate query."""
        return f"SELECT {function}({column}) FROM {table}"

    def _build_group_by(self, query, columns):
        """Add GROUP BY to query."""
        return f"{query} GROUP BY {', '.join(columns)}"

    def test_count_all(self):
        query = self._build_count("users")
        assert query == "SELECT COUNT(*) FROM users"

    def test_count_column(self):
        query = self._build_count("users", "email")
        assert query == "SELECT COUNT(email) FROM users"

    def test_sum_aggregate(self):
        query = self._build_aggregate("orders", "SUM", "amount")
        assert query == "SELECT SUM(amount) FROM orders"

    def test_avg_aggregate(self):
        query = self._build_aggregate("products", "AVG", "price")
        assert query == "SELECT AVG(price) FROM products"

    def test_group_by(self):
        query = self._build_count("orders")
        query = self._build_group_by(query, ["status", "user_id"])
        assert "GROUP BY status, user_id" in query


# --- Search query patterns ---


class TestSearchQuery:
    """Tests for full-text search query building."""

    def _build_text_search(self, columns, search_term):
        """Build text search condition."""
        conditions = [f"{col} LIKE ?" for col in columns]
        params = [f"%{search_term}%"] * len(columns)
        return "(" + " OR ".join(conditions) + ")", params

    def _escape_search_term(self, term):
        """Escape special characters in search term."""
        special_chars = ["%", "_", "[", "]"]
        for char in special_chars:
            term = term.replace(char, f"\\{char}")
        return term

    def _tokenize_search(self, search_string):
        """Tokenize search string into terms."""
        # Simple tokenization: split on whitespace, filter empty
        return [t.strip().lower() for t in search_string.split() if t.strip()]

    def test_single_column_search(self):
        condition, params = self._build_text_search(["title"], "python")
        assert "title LIKE ?" in condition
        assert params == ["%python%"]

    def test_multi_column_search(self):
        condition, params = self._build_text_search(
            ["title", "content"], "test"
        )
        assert "title LIKE ?" in condition
        assert "content LIKE ?" in condition
        assert "OR" in condition

    def test_escape_special_chars(self):
        escaped = self._escape_search_term("100%")
        assert escaped == "100\\%"

    def test_tokenize_search(self):
        tokens = self._tokenize_search("  Python  Web   Development  ")
        assert tokens == ["python", "web", "development"]


# --- Subquery patterns ---


class TestSubqueryBuilder:
    """Tests for subquery building patterns."""

    def _build_in_subquery(self, field, subquery):
        """Build IN subquery condition."""
        return f"{field} IN ({subquery})"

    def _build_exists_subquery(self, subquery):
        """Build EXISTS subquery condition."""
        return f"EXISTS ({subquery})"

    def _wrap_as_subquery(self, query, alias):
        """Wrap query as subquery with alias."""
        return f"({query}) AS {alias}"

    def test_in_subquery(self):
        sub = "SELECT user_id FROM active_sessions"
        condition = self._build_in_subquery("id", sub)
        assert condition == "id IN (SELECT user_id FROM active_sessions)"

    def test_exists_subquery(self):
        sub = "SELECT 1 FROM orders WHERE orders.user_id = users.id"
        condition = self._build_exists_subquery(sub)
        assert "EXISTS" in condition

    def test_wrap_as_subquery(self):
        query = "SELECT id, name FROM users"
        wrapped = self._wrap_as_subquery(query, "u")
        assert wrapped == "(SELECT id, name FROM users) AS u"


# --- Insert query patterns ---


class TestInsertQuery:
    """Tests for INSERT query building."""

    def _build_insert(self, table, columns, values):
        """Build INSERT query."""
        cols = ", ".join(columns)
        placeholders = ", ".join(["?"] * len(values))
        return f"INSERT INTO {table} ({cols}) VALUES ({placeholders})", values

    def _build_insert_from_dict(self, table, data):
        """Build INSERT from dictionary."""
        columns = list(data.keys())
        values = list(data.values())
        return self._build_insert(table, columns, values)

    def test_simple_insert(self):
        query, params = self._build_insert(
            "users", ["name", "email"], ["John", "john@example.com"]
        )
        assert "INSERT INTO users (name, email) VALUES (?, ?)" == query
        assert params == ["John", "john@example.com"]

    def test_insert_from_dict(self):
        data = {"name": "Jane", "status": "active"}
        query, params = self._build_insert_from_dict("users", data)
        assert "name" in query
        assert "status" in query


# --- Update query patterns ---


class TestUpdateQuery:
    """Tests for UPDATE query building."""

    def _build_update(self, table, updates, conditions=None):
        """Build UPDATE query."""
        set_parts = [f"{k} = ?" for k in updates.keys()]
        query = f"UPDATE {table} SET {', '.join(set_parts)}"
        params = list(updates.values())
        if conditions:
            query += f" WHERE {' AND '.join(conditions)}"
        return query, params

    def test_update_single_field(self):
        query, params = self._build_update("users", {"status": "active"})
        assert "UPDATE users SET status = ?" == query
        assert params == ["active"]

    def test_update_multiple_fields(self):
        query, params = self._build_update(
            "users", {"status": "active", "updated_at": "now"}
        )
        assert "status = ?" in query
        assert "updated_at = ?" in query

    def test_update_with_where(self):
        query, params = self._build_update(
            "users", {"status": "inactive"}, ["id = 123"]
        )
        assert "WHERE id = 123" in query


# --- Delete query patterns ---


class TestDeleteQuery:
    """Tests for DELETE query building."""

    def _build_delete(self, table, conditions=None):
        """Build DELETE query."""
        query = f"DELETE FROM {table}"
        if conditions:
            query += f" WHERE {' AND '.join(conditions)}"
        return query

    def _build_soft_delete(self, table, conditions):
        """Build soft delete (UPDATE) query."""
        return self._build_update_query(
            table, {"deleted_at": "CURRENT_TIMESTAMP"}, conditions
        )

    def _build_update_query(self, table, updates, conditions):
        """Helper for update query."""
        set_parts = [f"{k} = {v}" for k, v in updates.items()]
        query = f"UPDATE {table} SET {', '.join(set_parts)}"
        if conditions:
            query += f" WHERE {' AND '.join(conditions)}"
        return query

    def test_delete_all(self):
        query = self._build_delete("temp_data")
        assert query == "DELETE FROM temp_data"

    def test_delete_with_condition(self):
        query = self._build_delete("sessions", ["expired = 1"])
        assert "WHERE expired = 1" in query

    def test_soft_delete(self):
        query = self._build_soft_delete("users", ["id = 123"])
        assert "UPDATE" in query
        assert "deleted_at" in query


# --- Query validation patterns ---


class TestQueryValidation:
    """Tests for query validation patterns."""

    def _validate_table_name(self, name):
        """Validate table name is safe."""
        import re

        return bool(re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", name))

    def _validate_column_name(self, name):
        """Validate column name is safe."""
        import re

        # Allow table.column notation
        return bool(re.match(r"^[a-zA-Z_][a-zA-Z0-9_.]*$", name))

    def test_valid_table_name(self):
        assert self._validate_table_name("users") is True
        assert self._validate_table_name("user_profiles") is True

    def test_invalid_table_name(self):
        assert self._validate_table_name("users; DROP TABLE") is False
        assert self._validate_table_name("123invalid") is False

    def test_valid_column_name(self):
        assert self._validate_column_name("name") is True
        assert self._validate_column_name("users.name") is True

    def test_invalid_column_name(self):
        assert self._validate_column_name("name OR 1=1") is False
