"""Tests for SQL output generation."""

from datetime import date, datetime
from decimal import Decimal
from uuid import UUID

import pytest

from dbslice.config import DatabaseType
from dbslice.models import Column, Table
from dbslice.output.sql import SQLGenerator, generate_sql


class TestSQLGenerator:
    """Tests for SQLGenerator."""

    @pytest.fixture
    def generator(self):
        return SQLGenerator(db_type=DatabaseType.POSTGRESQL)

    @pytest.fixture
    def mysql_generator(self):
        return SQLGenerator(db_type=DatabaseType.MYSQL)

    @pytest.fixture
    def sample_tables_schema(self):
        return {
            "users": Table(
                name="users",
                schema="public",
                columns=[
                    Column("id", "INTEGER", False, True),
                    Column("email", "TEXT", False, False),
                    Column("name", "TEXT", True, False),
                ],
                primary_key=("id",),
                foreign_keys=[],
            ),
            "orders": Table(
                name="orders",
                schema="public",
                columns=[
                    Column("id", "INTEGER", False, True),
                    Column("user_id", "INTEGER", False, False),
                    Column("total", "DECIMAL", True, False),
                ],
                primary_key=("id",),
                foreign_keys=[],
            ),
        }

    def test_generate_basic(self, generator, sample_tables_schema):
        tables_data = {
            "users": [{"id": 1, "email": "test@example.com", "name": "Test"}],
            "orders": [{"id": 1, "user_id": 1, "total": Decimal("99.99")}],
        }
        insert_order = ["users", "orders"]

        sql = generator.generate(tables_data, insert_order, sample_tables_schema)

        assert "-- Generated by dbslice" in sql
        assert "BEGIN;" in sql
        assert "COMMIT;" in sql
        assert 'INSERT INTO "users"' in sql
        assert 'INSERT INTO "orders"' in sql

    def test_generate_with_schema(self, sample_tables_schema):
        generator = SQLGenerator(db_type=DatabaseType.POSTGRESQL, schema="myschema")
        tables_data = {
            "users": [{"id": 1, "email": "test@example.com", "name": "Test"}],
        }
        insert_order = ["users"]

        sql = generator.generate(tables_data, insert_order, sample_tables_schema)

        assert 'SET search_path TO "myschema", public;' in sql
        assert 'INSERT INTO "users"' in sql

    def test_generate_with_public_schema_no_search_path(self, sample_tables_schema):
        generator = SQLGenerator(db_type=DatabaseType.POSTGRESQL, schema="public")
        tables_data = {
            "users": [{"id": 1, "email": "test@example.com", "name": "Test"}],
        }
        insert_order = ["users"]

        sql = generator.generate(tables_data, insert_order, sample_tables_schema)

        assert "SET search_path" not in sql

    def test_insert_order_respected(self, generator, sample_tables_schema):
        tables_data = {
            "users": [{"id": 1, "email": "a@b.com", "name": None}],
            "orders": [{"id": 1, "user_id": 1, "total": None}],
        }
        insert_order = ["users", "orders"]

        sql = generator.generate(tables_data, insert_order, sample_tables_schema)

        # users should come before orders
        users_pos = sql.find('INSERT INTO "users"')
        orders_pos = sql.find('INSERT INTO "orders"')
        assert users_pos < orders_pos

    def test_format_value_none(self, generator):
        assert generator._format_value(None) == "NULL"

    def test_format_value_bool(self, generator, mysql_generator):
        assert generator._format_value(True) == "TRUE"
        assert generator._format_value(False) == "FALSE"
        assert mysql_generator._format_value(True) == "1"
        assert mysql_generator._format_value(False) == "0"

    def test_format_value_numbers(self, generator):
        assert generator._format_value(42) == "42"
        assert generator._format_value(3.14) == "3.14"
        assert generator._format_value(Decimal("99.99")) == "99.99"

    def test_format_value_string(self, generator):
        assert generator._format_value("hello") == "'hello'"

    def test_format_value_string_with_quotes(self, generator):
        result = generator._format_value("it's a test")
        assert result == "'it''s a test'"

    def test_format_value_datetime(self, generator):
        dt = datetime(2024, 1, 15, 10, 30, 0)
        result = generator._format_value(dt)
        assert "2024-01-15" in result
        assert "10:30:00" in result

    def test_format_value_date(self, generator):
        d = date(2024, 1, 15)
        result = generator._format_value(d)
        assert "2024-01-15" in result

    def test_format_value_uuid(self, generator):
        u = UUID("12345678-1234-5678-1234-567812345678")
        result = generator._format_value(u)
        assert "12345678-1234-5678-1234-567812345678" in result

    def test_format_value_dict(self, generator):
        data = {"key": "value", "num": 42}
        result = generator._format_value(data)
        assert "key" in result
        assert "value" in result

    def test_format_value_list(self, generator):
        data = [1, 2, 3]
        result = generator._format_value(data)
        assert "[1, 2, 3]" in result

    def test_format_value_bytes_postgres(self, generator):
        data = b"\x00\x01\x02"
        result = generator._format_value(data)
        assert "000102" in result
        assert "E'" in result or "\\x" in result

    def test_escape_string_backslash_postgres(self, generator):
        result = generator._escape_string("path\\to\\file")
        # PostgreSQL should use E'' syntax for backslashes
        assert "E'" in result or "'" in result

    def test_quote_identifier_postgres(self, generator):
        assert generator._quote_identifier("users") == '"users"'
        assert generator._quote_identifier("order") == '"order"'

    def test_quote_identifier_mysql(self, mysql_generator):
        assert mysql_generator._quote_identifier("users") == "`users`"

    def test_disable_fk_checks(self, generator, mysql_generator):
        pg_sql = generator._disable_fk_checks()
        assert "DEFERRED" in pg_sql

        mysql_sql = mysql_generator._disable_fk_checks()
        assert "FOREIGN_KEY_CHECKS" in mysql_sql

    def test_enable_fk_checks(self, generator, mysql_generator):
        pg_sql = generator._enable_fk_checks()
        assert "IMMEDIATE" in pg_sql

        mysql_sql = mysql_generator._enable_fk_checks()
        assert "FOREIGN_KEY_CHECKS" in mysql_sql

    def test_include_truncate(self, sample_tables_schema):
        generator = SQLGenerator(
            db_type=DatabaseType.POSTGRESQL,
            include_truncate=True,
        )
        tables_data = {"users": [{"id": 1, "email": "a@b.com", "name": None}]}
        sql = generator.generate(tables_data, ["users"], sample_tables_schema)
        assert "TRUNCATE TABLE" in sql

    def test_no_transaction(self, sample_tables_schema):
        generator = SQLGenerator(
            db_type=DatabaseType.POSTGRESQL,
            include_transaction=False,
        )
        tables_data = {"users": [{"id": 1, "email": "a@b.com", "name": None}]}
        sql = generator.generate(tables_data, ["users"], sample_tables_schema)
        assert "BEGIN;" not in sql
        assert "COMMIT;" not in sql


class TestGenerateSqlFunction:
    """Tests for the generate_sql convenience function."""

    def test_basic_usage(self):
        tables_data = {"users": [{"id": 1, "email": "test@test.com"}]}
        tables_schema = {
            "users": Table(
                name="users",
                schema="public",
                columns=[
                    Column("id", "INTEGER", False, True),
                    Column("email", "TEXT", False, False),
                ],
                primary_key=("id",),
                foreign_keys=[],
            )
        }

        sql = generate_sql(
            tables_data,
            ["users"],
            tables_schema,
            DatabaseType.POSTGRESQL,
        )

        assert 'INSERT INTO "users"' in sql
        assert "test@test.com" in sql
