"""
Builder 模块测试
测试 MySQLBuilder、PostgreSQLBuilder 和 SQLBuilderFactory
"""
import pytest
from xll_kit.database import (
    MySQLBuilder,
    PostgreSQLBuilder,
    SQLBuilderFactory,
    SQLResult,
)


class TestMySQLBuilder:
    """MySQL Builder 测试"""

    def setup_method(self):
        """每个测试方法前执行"""
        self.builder = MySQLBuilder()

    def test_build_insert(self, mock_users_table, sample_user):
        """测试单条插入"""
        result = self.builder.build_insert(mock_users_table, sample_user)

        assert isinstance(result, SQLResult)
        assert "INSERT INTO users" in result.sql
        assert "VALUES" in result.sql
        assert result.params == sample_user
        assert ":name" in result.sql
        assert ":email" in result.sql
        assert ":age" in result.sql

    def test_build_batch_insert(self, mock_users_table, sample_users):
        """测试批量插入"""
        result = self.builder.build_batch_insert(mock_users_table, sample_users)

        assert isinstance(result, SQLResult)
        assert "INSERT INTO users" in result.sql
        assert "VALUES" in result.sql
        # 参数数量 = 数据行数 * 每行字段数
        assert len(result.params) == len(sample_users) * len(sample_users[0])
        # 检查参数命名格式
        assert 'name_0' in result.params
        assert 'email_1' in result.params
        assert 'age_2' in result.params

    def test_build_batch_insert_empty_list(self, mock_users_table):
        """测试空数据列表抛出异常"""
        with pytest.raises(ValueError, match="data_list cannot be empty"):
            self.builder.build_batch_insert(mock_users_table, [])

    def test_build_upsert(self, mock_users_table, sample_user):
        """测试 Upsert (ON DUPLICATE KEY UPDATE)"""
        result = self.builder.build_upsert(
            mock_users_table,
            sample_user,
            conflict_target=['email']
        )

        assert "INSERT INTO users" in result.sql
        assert "ON DUPLICATE KEY UPDATE" in result.sql
        assert "name = VALUES(name)" in result.sql
        assert "age = VALUES(age)" in result.sql
        # email 是冲突键，不应该在 UPDATE 中
        assert "email = VALUES(email)" not in result.sql

    def test_build_batch_upsert(self, mock_users_table, sample_users):
        """测试批量 Upsert"""
        result = self.builder.build_batch_upsert(
            mock_users_table,
            sample_users,
            conflict_target=['email']
        )

        assert "INSERT INTO users" in result.sql
        assert "ON DUPLICATE KEY UPDATE" in result.sql
        assert len(result.params) > 0

    def test_build_update(self, mock_users_table):
        """测试单条更新"""
        data = {'name': 'Alice Updated', 'age': 26}
        where = {'id': 1}

        result = self.builder.build_update(mock_users_table, data, where)

        assert "UPDATE users" in result.sql
        assert "SET" in result.sql
        assert "WHERE" in result.sql
        assert "set_name" in result.params
        assert "set_age" in result.params
        assert "where_id" in result.params
        assert result.params['set_name'] == 'Alice Updated'
        assert result.params['where_id'] == 1

    def test_build_batch_update(self, mock_users_table):
        """测试批量更新"""
        data_list = [
            {'id': 1, 'name': 'Alice V2', 'age': 27},
            {'id': 2, 'name': 'Bob V2', 'age': 31}
        ]

        result = self.builder.build_batch_update(
            mock_users_table,
            data_list,
            key_columns=['id']
        )

        assert "UPDATE users" in result.sql
        assert "CASE" in result.sql
        assert "WHEN" in result.sql
        assert "WHERE" in result.sql
        assert len(result.params) > 0

    def test_build_delete(self, mock_users_table):
        """测试删除"""
        conditions = {'id': 1, 'email': 'test@example.com'}

        result = self.builder.build_delete(mock_users_table, conditions)

        assert "DELETE FROM users" in result.sql
        assert "WHERE" in result.sql
        assert result.params == conditions
        assert ":id" in result.sql
        assert ":email" in result.sql

    def test_get_columns(self, sample_users):
        """测试获取列名"""
        columns = self.builder._get_columns(sample_users)

        assert columns == ['name', 'email', 'age']

    def test_get_columns_empty_list(self):
        """测试空列表返回空列名"""
        columns = self.builder._get_columns([])

        assert columns == []


class TestPostgreSQLBuilder:
    """PostgreSQL Builder 测试"""

    def setup_method(self):
        self.builder = PostgreSQLBuilder()

    def test_build_insert(self, mock_users_table, sample_user):
        """测试 PostgreSQL 插入"""
        result = self.builder.build_insert(mock_users_table, sample_user)

        assert isinstance(result, SQLResult)
        assert "INSERT INTO users" in result.sql
        assert result.params == sample_user

    def test_build_upsert(self, mock_users_table, sample_user):
        """测试 PostgreSQL Upsert (ON CONFLICT)"""
        result = self.builder.build_upsert(
            mock_users_table,
            sample_user,
            conflict_target=['email']
        )

        assert "INSERT INTO users" in result.sql
        assert "ON CONFLICT (email)" in result.sql
        assert "DO UPDATE SET" in result.sql
        assert "EXCLUDED.name" in result.sql
        assert "EXCLUDED.age" in result.sql

    def test_build_upsert_do_nothing(self, mock_users_table):
        """测试 PostgreSQL Upsert DO NOTHING"""
        data = {'email': 'test@example.com'}
        result = self.builder.build_upsert(
            mock_users_table,
            data,
            conflict_target=['email']
        )

        # 没有非冲突字段要更新，应该是 DO NOTHING
        assert "DO NOTHING" in result.sql
        assert "DO UPDATE" not in result.sql

    def test_build_batch_update(self, mock_users_table):
        """测试 PostgreSQL 批量更新"""
        data_list = [
            {'id': 1, 'name': 'Alice V2', 'age': 27},
            {'id': 2, 'name': 'Bob V2', 'age': 31}
        ]

        result = self.builder.build_batch_update(
            mock_users_table,
            data_list,
            key_columns=['id']
        )

        assert "UPDATE users" in result.sql
        assert "FROM (VALUES" in result.sql
        assert "AS data" in result.sql
        assert "WHERE" in result.sql


class TestSQLBuilderFactory:
    """Builder Factory 测试"""

    def test_get_mysql_builder(self):
        """测试获取 MySQL Builder"""
        builder = SQLBuilderFactory.get_builder('mysql')
        assert isinstance(builder, MySQLBuilder)

    def test_get_postgresql_builder(self):
        """测试获取 PostgreSQL Builder"""
        builder = SQLBuilderFactory.get_builder('postgresql')
        assert isinstance(builder, PostgreSQLBuilder)

    def test_get_sqlite_builder(self):
        """测试 SQLite（使用 PostgreSQL 语法）"""
        builder = SQLBuilderFactory.get_builder('sqlite')
        assert isinstance(builder, PostgreSQLBuilder)

    def test_get_invalid_dialect(self):
        """测试无效的数据库类型"""
        with pytest.raises(ValueError, match="Unsupported database dialect"):
            SQLBuilderFactory.get_builder('oracle')

    def test_register_builder(self):
        """测试注册自定义 Builder"""

        class CustomBuilder(MySQLBuilder):
            pass

        SQLBuilderFactory.register_builder('custom', CustomBuilder)
        builder = SQLBuilderFactory.get_builder('custom')
        assert isinstance(builder, CustomBuilder)

    def test_register_invalid_builder(self):
        """测试注册无效的 Builder"""

        class NotABuilder:
            pass

        with pytest.raises(TypeError):
            SQLBuilderFactory.register_builder('invalid', NotABuilder)


class TestSQLResult:
    """SQLResult 数据类测试"""

    def test_sql_result_creation(self):
        """测试创建 SQLResult"""
        result = SQLResult(
            sql="INSERT INTO users VALUES (:name)",
            params={'name': 'Alice'}
        )

        assert result.sql == "INSERT INTO users VALUES (:name)"
        assert result.params == {'name': 'Alice'}

    def test_sql_result_repr(self):
        """测试 SQLResult 的字符串表示"""
        result = SQLResult(
            sql="SELECT * FROM users WHERE id = :id",
            params={'id': 1}
        )

        repr_str = repr(result)
        assert "SQLResult" in repr_str
        assert "1 items" in repr_str