"""Tests for db_core.schema.SchemaBuilder — pure SQL string generation.

No database connection needed — all tests assert against expected SQL strings.
"""
from __future__ import annotations

import sys
from pathlib import Path

parent_dir = Path(__file__).parent.parent.parent
if str(parent_dir) not in sys.path:
    sys.path.insert(0, str(parent_dir))

from db_core.schema import SchemaBuilder, TableDef, ColumnDef, IndexDef, ForeignKeyDef


# ---------------------------------------------------------------------------
# create_table
# ---------------------------------------------------------------------------

def test_create_table_basic():
    """Simple table with two columns."""
    t = TableDef(
        name="users",
        columns=[
            ColumnDef("id", "SERIAL", primary_key=True),
            ColumnDef("name", "TEXT", nullable=False),
        ],
    )
    sql = SchemaBuilder.create_table(t)
    assert sql == (
        "CREATE TABLE IF NOT EXISTS users (\n"
        "  id SERIAL PRIMARY KEY,\n"
        "  name TEXT NOT NULL\n"
        ")"
    )


def test_create_table_with_defaults():
    """Columns with DEFAULT expressions."""
    t = TableDef(
        name="logs",
        columns=[
            ColumnDef("id", "SERIAL", primary_key=True),
            ColumnDef("created_at", "TIMESTAMPTZ", default="NOW()"),
            ColumnDef("is_active", "BOOLEAN", default="TRUE"),
            ColumnDef("metadata", "JSONB", default="'{}'::jsonb"),
        ],
    )
    sql = SchemaBuilder.create_table(t)
    assert "DEFAULT NOW()" in sql
    assert "DEFAULT TRUE" in sql
    assert "DEFAULT '{}'::jsonb" in sql


def test_create_table_with_foreign_keys():
    """Table with foreign key constraints."""
    t = TableDef(
        name="posts",
        columns=[
            ColumnDef("id", "SERIAL", primary_key=True),
            ColumnDef("user_id", "INTEGER", nullable=False),
            ColumnDef("title", "TEXT"),
        ],
        foreign_keys=[
            ForeignKeyDef("user_id", "users", "id", on_delete="CASCADE"),
        ],
    )
    sql = SchemaBuilder.create_table(t)
    assert "CONSTRAINT fk_posts_user_id_users" in sql
    assert "FOREIGN KEY (user_id)" in sql
    assert "REFERENCES users(id)" in sql
    assert "ON DELETE CASCADE" in sql


def test_create_table_without_if_not_exists():
    """Table without IF NOT EXISTS."""
    t = TableDef(
        name="t",
        columns=[ColumnDef("id", "INTEGER")],
        if_not_exists=False,
    )
    sql = SchemaBuilder.create_table(t)
    assert sql.startswith("CREATE TABLE t (")
    assert "IF NOT EXISTS" not in sql


def test_create_table_with_uuid_pk():
    """UUID primary key with gen_random_uuid() default."""
    t = TableDef(
        name="accounts",
        columns=[
            ColumnDef("id", "UUID", primary_key=True, default="gen_random_uuid()"),
            ColumnDef("phone", "VARCHAR(20)", nullable=False),
        ],
    )
    sql = SchemaBuilder.create_table(t)
    assert "id UUID PRIMARY KEY DEFAULT gen_random_uuid()" in sql
    assert "phone VARCHAR(20) NOT NULL" in sql


# ---------------------------------------------------------------------------
# create_index
# ---------------------------------------------------------------------------

def test_create_index_btree():
    idx = IndexDef(["email"])
    sql = SchemaBuilder.create_index("users", idx)
    assert sql == "CREATE INDEX IF NOT EXISTS ix_users_email ON users (email)"


def test_create_index_unique():
    idx = IndexDef(["username"], unique=True)
    sql = SchemaBuilder.create_index("users", idx)
    assert sql == "CREATE UNIQUE INDEX IF NOT EXISTS uq_users_username ON users (username)"


def test_create_index_gin():
    idx = IndexDef(["metadata"], using="gin")
    sql = SchemaBuilder.create_index("items", idx)
    assert "USING gin" in sql
    assert "ix_items_metadata" in sql


def test_create_index_composite():
    idx = IndexDef(["channel_id", "created_at"])
    sql = SchemaBuilder.create_index("messages", idx)
    assert "ix_messages_channel_id_created_at" in sql
    assert "(channel_id, created_at)" in sql


def test_create_index_explicit_name():
    idx = IndexDef(["email"], unique=True, name="uq_email_special")
    sql = SchemaBuilder.create_index("users", idx)
    assert sql == "CREATE UNIQUE INDEX IF NOT EXISTS uq_email_special ON users (email)"


def test_create_index_bloom():
    """Bloom filter index with WITH (length=80)."""
    idx = IndexDef(["url"], using="bloom", with_params={"length": 80})
    sql = SchemaBuilder.create_index("web_discovered_urls", idx)
    assert sql == (
        "CREATE INDEX IF NOT EXISTS ix_web_discovered_urls_url "
        "ON web_discovered_urls USING bloom (url) WITH (length = 80)"
    )


def test_create_index_bloom_with_int_params():
    """Bloom with integer-valued WITH params — no quotes."""
    idx = IndexDef(["url"], using="bloom", with_params={"length": 160})
    sql = SchemaBuilder.create_index("t", idx)
    assert "WITH (length = 160)" in sql


def test_create_index_bloom_with_string_params():
    """WITH params get single-quoted when the value is a string."""
    idx = IndexDef(["col"], using="bloom", with_params={"length": 80, "col": "4"})
    sql = SchemaBuilder.create_index("t", idx)
    assert "WITH (length = 80, col = '4')" in sql


def test_create_index_bloom_multiple_columns():
    """Bloom index on multiple columns."""
    idx = IndexDef(["a", "b"], using="bloom", with_params={"length": 80})
    sql = SchemaBuilder.create_index("t", idx)
    assert "USING bloom" in sql
    assert "(a, b)" in sql
    assert "WITH (length = 80)" in sql


def test_create_index_with_params_is_none():
    """with_params=None produces no WITH clause."""
    idx = IndexDef(["email"], using="bloom")
    sql = SchemaBuilder.create_index("users", idx)
    assert "WITH" not in sql
    assert "USING bloom" in sql


def test_create_index_gin_backward_compat():
    """GIN index with with_params=None still works (backward compat)."""
    idx = IndexDef(["metadata"], using="gin", with_params=None)
    sql = SchemaBuilder.create_index("items", idx)
    assert "USING gin" in sql
    assert "WITH" not in sql


# ---------------------------------------------------------------------------
# drop_table / drop_index
# ---------------------------------------------------------------------------

def test_drop_table():
    assert SchemaBuilder.drop_table("users") == "DROP TABLE IF EXISTS users"


def test_drop_table_no_if_exists():
    assert SchemaBuilder.drop_table("users", if_exists=False) == "DROP TABLE users"


def test_drop_index():
    assert SchemaBuilder.drop_index("ix_users_email") == "DROP INDEX IF EXISTS ix_users_email"


# ---------------------------------------------------------------------------
# ALTER TABLE operations
# ---------------------------------------------------------------------------

def test_add_column():
    col = ColumnDef("bio", "TEXT")
    sql = SchemaBuilder.add_column("users", col)
    assert sql == "ALTER TABLE users ADD COLUMN bio TEXT"


def test_add_column_not_null_default():
    col = ColumnDef("status", "VARCHAR(20)", nullable=False, default="'active'")
    sql = SchemaBuilder.add_column("users", col)
    assert "NOT NULL" in sql
    assert "DEFAULT 'active'" in sql


def test_drop_column():
    assert SchemaBuilder.drop_column("users", "bio") == "ALTER TABLE users DROP COLUMN IF EXISTS bio"


def test_rename_table():
    assert SchemaBuilder.rename_table("old_items", "new_items") == "ALTER TABLE old_items RENAME TO new_items"


# ---------------------------------------------------------------------------
# Edge cases
# ---------------------------------------------------------------------------

def test_table_def_empty_indexes_and_fks():
    """TableDef with default-empty indexes and foreign_keys."""
    t = TableDef(
        name="simple",
        columns=[ColumnDef("id", "INTEGER")],
    )
    sql = SchemaBuilder.create_table(t)
    assert "CONSTRAINT" not in sql
    assert "INDEX" not in sql


def test_create_table_multiple_columns():
    """Many columns, verify ordering and commas."""
    t = TableDef(
        name="t",
        columns=[
            ColumnDef("a", "INTEGER"),
            ColumnDef("b", "TEXT"),
            ColumnDef("c", "BOOLEAN", default="FALSE"),
            ColumnDef("d", "TIMESTAMPTZ", default="NOW()"),
        ],
    )
    sql = SchemaBuilder.create_table(t)
    lines = [l.strip() for l in sql.split("\n") if l.strip() and not l.strip().startswith("CREATE") and not l.strip() == ")"]
    assert len(lines) == 4  # 4 columns
    # first line ends with comma? actually last line before ) should NOT have comma
    assert ",  a INTEGER" not in sql  # no leading comma
