#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from pymysql.cursors import DictCursor
from simpysql.Util.Expression import expression as expr, Expression
from simpysql.Util.Response import Response
from .BaseBuilder import BaseBuilder
from simpysql.Util.Dynamic import Dynamic


class MysqlBuilder(BaseBuilder):
    operators = [
        '=', '<', '>', '<=', '>=', '<>', '!=',
        'is', 'like', 'like binary', 'not like', 'between', 'ilike',
        '&', '|', '^', '<<', '>>',
        'rlike', 'regexp', 'not regexp',
        '~', '~*', '!~', '!~*', 'similar to',
        'not similar to', 'not ilike', '~~*', '!~~*', 'in', 'not in', 'not between', 'is not'
    ]

    def __init__(self, model, alias=None):
        self.__model__ = model
        self.__alias__ = alias
        self.__where__ = []
        self.__orwhere__ = []  # orwhere处理逻辑
        self.__whereor__ = []  # orwhere处理逻辑
        self.__select__ = []  # 检索的字段
        self.__limit__ = None  # 检索的数据条数
        self.__orderby__ = []  # 排序字段
        self.__groupby__ = []  # 排序字段
        self.__offset__ = None  # offset
        self.__lock__ = None  # lock
        self.__join__ = []  # leftjoin
        self.__union__ = []  # union & unionall
        self.__on__ = []  # leftjoin
        self.__having__ = None  # having
        self.__subquery__ = []  # subquery

    def first(self):
        # 暂存原有的 limit
        original_limit = self.__limit__
        self.__limit__ = 1
        data = self.get()
        # 恢复原有状态
        self.__limit__ = original_limit
        if data:
            return data.pop()
        return None

    def get(self):
        return [Dynamic(index) for index in self._get_connection().execute(self._compile_select(), DictCursor)]

    def lists(self, columns):
        return Response(self._get_connection().execute(self._compile_select(), DictCursor)).tolist(columns)

    def pluck(self, key, value):
        return Response(self._get_connection().execute(self._compile_select(), DictCursor)).pluck(key, value)

    def data(self):
        return Response(self._get_connection().execute(self._compile_select(), DictCursor)).data()

    def response(self):
        return Response(self._get_connection().execute(self._compile_select(), DictCursor))

    def _aggregate(self, func_name, column=None):
        """
        内部方法：执行聚合查询（DRY 原则重构）
        :param func_name: 聚合函数名 (max, min, avg, sum, count)
        :param column: 列名，count时可为None表示 count(*)
        :return: 聚合结果
        """
        # 暂存原有状态
        original_select = self.__select__
        
        # 构建聚合SQL
        if column is None:
            self.__select__ = ['{}(*) as aggregate'.format(func_name)]
        else:
            self.__select__ = ['{}({}) as aggregate'.format(func_name, column)]
        
        data = self.first()
        
        # 恢复原有状态
        self.__select__ = original_select
        
        if data is None:
            return None
        
        result = data['aggregate']
        # avg 返回浮点数，count 返回整数，其他尝试转换为合适的类型
        if func_name == 'avg':
            return float(result) if result is not None else None
        elif func_name == 'count':
            return int(result) if result is not None else 0
        elif func_name in ('sum', 'max', 'min'):
            # 尝试转换为数值类型
            if result is not None:
                try:
                    if '.' in str(result):
                        return float(result)
                    return int(result)
                except (ValueError, TypeError):
                    return result
        return result

    def max(self, column):
        """获取指定列的最大值"""
        if isinstance(column, str) and column in self.__model__.columns:
            return self._aggregate('max', column)
        raise Exception('param invalid in function max')

    def min(self, column):
        """获取指定列的最小值"""
        if isinstance(column, str) and column in self.__model__.columns:
            return self._aggregate('min', column)
        raise Exception('param invalid in function min')

    def avg(self, column):
        """获取指定列的平均值"""
        if isinstance(column, str) and column in self.__model__.columns:
            return self._aggregate('avg', column)
        raise Exception('param invalid in function avg')

    def sum(self, column):
        """获取指定列的总和"""
        if isinstance(column, str) and column in self.__model__.columns:
            return self._aggregate('sum', column)
        raise Exception('param invalid in function sum')

    def count(self, column=None):
        """获取记录数量"""
        if column is None:
            return self._aggregate('count')
        if isinstance(column, str) and column in self.__model__.columns:
            return self._aggregate('count', column)
        raise Exception('param invalid in function count')

    def exist(self):
        # 暂存原有的 limit
        original_limit = self.__limit__
        self.__limit__ = 1
        result = True if len(self.get()) > 0 else False
        # 恢复原有状态
        self.__limit__ = original_limit
        return result

    def update(self, data):
        # 增加安全防护：禁止无条件全表更新
        if not self.__where__ and not self.__orwhere__ and not self.__whereor__:
            raise Exception("Update missing WHERE clause. This will update the entire table!")

        if data and isinstance(data, dict):
            data = self._set_update_time(data)
            data = {key: value for key, value in data.items() if key in self.__model__.columns}
            if not data:  # 防止过滤后 data 为空导致语法错误
                return 0
            return self._get_connection().execute(self._compile_update(data))

    def increment(self, key, amount=1):
        if not (isinstance(amount, int) and amount > 0):
            raise ValueError('increment amount must be a positive integer')
        data = {}
        data[key] = '{}+{}'.format(expr.format_column(key, self.__model__), str(amount))
        data = self._set_crease_update_time(data)
        return self._get_connection().execute(self._compile_increment(data))

    def decrement(self, key, amount=1):
        if not (isinstance(amount, int) and amount > 0):
            raise ValueError('decrement amount must be a positive integer')
        data = {}
        data[key] = '{}-{}'.format(expr.format_column(key, self.__model__), str(amount))
        data = self._set_crease_update_time(data)
        return self._get_connection().execute(self._compile_increment(data))

    def create(self, data):
        if data:
            if data and isinstance(data, dict):
                data = [{key: value for key, value in data.items() if key in self.__model__.columns}]
            if isinstance(data, list):
                data = [{k: v for k, v in value.items() if k in self.__model__.columns} for value in data]
            data = self._set_create_time(data)
            self._get_connection().execute(self._compile_create(data))
        return self

    def insert_on_duplicate(self, data):
        if isinstance(data, dict):
            data = [data]
        if isinstance(data, list):
            data = self._set_create_time(data)
            sql = self._compile_create(data) + ' on duplicate key update ' + \
                  self._compile_duplicate_data(list(data[0].keys()))
            self._get_connection().execute(sql)
        return self

    def insert(self, columns, data):
        self._get_connection().execute(self._compile_insert(columns, data))
        return self

    def insert_ignore(self, data):
        if data:
            if data and isinstance(data, dict):
                data = [{key: value for key, value in data.items() if key in self.__model__.columns}]
            if isinstance(data, list):
                data = [{k: v for k, v in value.items() if k in self.__model__.columns} for value in data]
            data = self._set_create_time(data)
            self._get_connection().execute(self._compile_insert_ignore(data))
        return self

    def replace(self, data):
        if data:
            if isinstance(data, dict):
                data = [data]
            if isinstance(data, list):
                data = self._set_create_time(data)
                return self._get_connection().execute(self._compile_replace(data))
            else:
                raise Exception('replace param invalid exception')
        return self

    def lastid(self):
        data = self._get_connection().execute(self._compile_lastid())
        return data[0][0] if data and data[0] and data[0][0] else None

    def delete(self):
        # 增加安全防护：禁止无条件全表删除
        if not self.__where__ and not self.__orwhere__ and not self.__whereor__:
            raise Exception("Delete missing WHERE clause. This will delete the entire table!")

        return self._get_connection().execute(self._compile_delete())

    def take(self, number):
        if number <= 0:
            raise Exception('take number invalid')
        self.__limit__ = int(number)
        return self

    def select(self, *args):
        self.__select__ = self._format_columns(list(args))
        return self

    def groupby(self, *args):
        self.__groupby__ = self._format_columns(list(args))
        return self

    def offset(self, number):
        if number < 0:
            raise Exception('offset number invalid')
        self.__offset__ = int(number)
        return self

    def tosql(self):
        return self._compile_select()

    def where(self, *args):
        length = args.__len__()
        if length == 1 and isinstance(args[0], dict):
            self.__where__.append(args[0])
        elif length == 2:
            self.__where__.append({args[0]: self._check_columns_value(args[1])})
        elif length == 3:
            if args[1] in self.operators:
                if args[1] == '=':
                    self.__where__.append({args[0]: self._check_columns_value(args[2])})
                else:
                    self.__where__.append((args[0], args[1], self._check_columns_value(args[2])))
            else:
                raise Exception('operator key world not found: "{}"'.format(args[1]))
        else:
            raise Exception('bad parameters in where function')
        return self

    def orwhere(self, *args):
        length = args.__len__()
        if length == 1 and isinstance(args[0], dict):
            self.__orwhere__.append(args[0])
        elif length == 1 and isinstance(args[0], list):
            self.__orwhere__.append(args[0])
        elif length == 2:
            self.__orwhere__.append({args[0]: args[1]})
        elif length == 3:
            if args[1] in self.operators:
                if args[1] == '=':
                    self.__orwhere__.append({args[0]: args[2]})
                else:
                    self.__orwhere__.append((args[0], args[1], args[2]))
            else:
                raise Exception('operator key world not found: "{}"'.format(args[1]))
        else:
            raise Exception('bad parameters in where function')
        return self

    def whereor(self, *args):
        length = args.__len__()
        if length == 1 and isinstance(args[0], list):
            self.__whereor__.append(args[0])
        else:
            raise Exception('bad parameters in where function')
        return self

    def orderby(self, column, direction='asc'):
        if direction.lower() == 'asc':
            self.__orderby__.append(expr.format_column(column, self.__model__))
        else:
            self.__orderby__.append(expr.format_column(column, self.__model__) + ' desc')
        return self

    def execute(self, sql):
        return Response(self._get_connection().execute(sql, DictCursor))

    def having(self, *args):
        length = args.__len__()
        if length == 2:
            self.__having__ = ' having {} {} {}'.format(args[0], '=', expr.format_string(args[1]))
        elif length == 3:
            self.__having__ = ' having {} {} {}'.format(args[0], args[1], expr.format_string(args[2]))
        else:
            raise Exception('invalid parameter in having function')
        return self

    def lock_for_update(self):
        self.__lock__ = ' for update'
        return self

    def lock_for_share(self):
        self.__lock__ = ' lock in share mode'
        return self

    def leftjoin(self, model):
        if not (isinstance(model, BaseBuilder)):
            raise TypeError('invalid parameter type in leftjoin')
        self.__join__.append(('left join', model))
        return self

    def rightjoin(self, model):
        if not (isinstance(model, BaseBuilder)):
            raise TypeError('invalid parameter type in rightjoin')
        self.__join__.append(('right join', model))
        return self

    def join(self, model):
        return self.innerjoin(model)

    def innerjoin(self, model):
        if not (isinstance(model, BaseBuilder)):
            raise TypeError('invalid parameter type in innerjoin')
        self.__join__.append(('inner join', model))
        return self

    def union(self, model):
        if not (isinstance(model, BaseBuilder)):
            raise TypeError('invalid parameter type in union')
        self.__union__.append(('union', model))
        return self

    def unionall(self, model):
        if not (isinstance(model, BaseBuilder)):
            raise TypeError('invalid parameter type in unionall')
        self.__union__.append(('union all', model))
        return self

    def on(self, *args):
        length = args.__len__()
        if length == 2:
            self.__on__.append((args[0], '=', args[1]))
        elif length == 3:
            self.__on__.append((args[0], args[1], args[2]))
        else:
            raise Exception('invalid parameter in on function')
        return self

    def subquery(self, model, alias='tmp'):
        self.__subquery__.append((alias, model))
        return self

    def create_or_update(self, data):
        if self.exist():
            return self.update(data)
        return self.create(data)

    def _compile_select(self):
        # 使用局部变量，不污染 self.__select__
        select_columns = self.__select__ if len(self.__select__) > 0 else ['*']
        # 对于 UNION 查询，ORDER BY/LIMIT/OFFSET 应该应用于整个 UNION 结果
        # 而不是第一个查询
        if self.__union__:
            # 第一个查询不包含 ORDER BY/LIMIT/OFFSET
            subsql = ''.join(
                [self._compile_where(), self._compile_whereor(), self._compile_orwhere(), self._compile_groupby(),
                 self._compile_having(), self._compile_lock()])
            joinsql = ''.join(self._compile_leftjoin())
            returnsql = "select {} from {}{}{}".format(','.join(select_columns), self._tablename(), joinsql, subsql)
            # ORDER BY/LIMIT/OFFSET 应用于整个 UNION
            union_suffix = ''.join([self._compile_orderby(), self._compile_limit(), self._compile_offset()])
            return '({})'.format(returnsql) + self._compile_union() + union_suffix
        else:
            subsql = ''.join(
                [self._compile_where(), self._compile_whereor(), self._compile_orwhere(), self._compile_groupby(),
                 self._compile_orderby(),
                 self._compile_having(), self._compile_limit(), self._compile_offset(), self._compile_lock()])
            joinsql = ''.join(self._compile_leftjoin())
            returnsql = "select {} from {}{}{}".format(','.join(select_columns), self._tablename(), joinsql, subsql)
            return returnsql

    def _compile_create(self, data):
        return "insert into {} {} values {}".format(self._tablename(), self._columnize(data[0]), self._valueize(data))

    def _compile_replace(self, data):
        return "replace into {} {} values {}".format(self._tablename(), self._columnize(data[0]), self._valueize(data))

    def _compile_insert(self, columns, data):
        # 避免元组转字符串的坑，确保值被正确格式化
        values_list = []
        for index in data:
            row_values = ','.join([str(expr.format_string(v)) for v in index])
            values_list.append('({})'.format(row_values))
        return "insert into {} {} values {}".format(self._tablename(), self._columnize(columns), ','.join(values_list))

    def _compile_insert_ignore(self, data):
        return "insert ignore into {} {} values {}".format(self._tablename(), self._columnize(data[0]), self._valueize(data))

    def _compile_update(self, data):
        where_clause = ''.join([self._compile_where(), self._compile_whereor(), self._compile_orwhere()])
        joinsql = ''.join(self._compile_leftjoin())
        # 如果有 JOIN，需要在 SET 子句中添加表别名前缀，避免字段歧义
        if self.__join__:
            # 获取主表别名或表名
            if self.__alias__:
                table_prefix = self.__alias__
            else:
                table_prefix = self.__model__.__tablename__
            set_clause = ','.join(['{}.{}={}'.format(table_prefix, expr.format_column(index, self.__model__), expr.format_string(value)) for index, value in data.items()])
        else:
            set_clause = ','.join(self._compile_dict(data))
        return "update {}{} set {}{}".format(self._tablename(), joinsql, set_clause, where_clause)

    def _compile_increment(self, data):
        subsql = ','.join(
            ['{}={}'.format(expr.format_column(index, self.__model__), value) for index, value in data.items()])
        return "update {} set {}{}".format(self._tablename(), subsql, self._compile_where())

    def _compile_delete(self):
        where_clause = ''.join([self._compile_where(), self._compile_whereor(), self._compile_orwhere()])
        joinsql = ''.join(self._compile_leftjoin())
        # MySQL DELETE JOIN 语法: DELETE T1 FROM table1 T1 JOIN table2 T2 ON ...
        # 如果有 JOIN，需要使用特殊语法
        if self.__join__:
            # 获取主表别名或表名
            if self.__alias__:
                main_table_ref = self.__alias__
            else:
                main_table_ref = self.__model__.__tablename__
            return 'delete {} from {}{}{}'.format(main_table_ref, self._tablename(), joinsql, where_clause)
        return 'delete from {}{}'.format(self._tablename(), where_clause)

    def _compile_lastid(self):
        return 'select last_insert_id() as lastid'

    def _columnize(self, columns):
        # 强制格式化为 (`col1`, `col2`) 形式，避免单元素末尾多逗号
        return '({})'.format(','.join(['`{}`'.format(c) for c in columns]))

    def _valueize(self, data):
        # 同样避免元组转字符串的坑，确保值被正确格式化
        values_list = []
        for index in data:
            row_values = ','.join([str(expr.format_string(v)) for v in index.values()])
            values_list.append('({})'.format(row_values))
        return ','.join(values_list)

    def _compile_groupby(self):
        return '' if len(self.__groupby__) == 0 else ' group by ' + ','.join(self.__groupby__)

    def _compile_orderby(self):
        return '' if len(self.__orderby__) == 0 else ' order by ' + ','.join(self.__orderby__)

    def _compile_limit(self):
        return '' if self.__limit__ is None else ' limit {}'.format(self.__limit__)

    def _compile_offset(self):
        return '' if self.__offset__ is None else ' offset {}'.format(self.__offset__)

    def _compile_lock(self):
        return '' if self.__lock__ is None else self.__lock__

    def _compile_leftjoin(self):
        if self.__join__:
            return ' ' + ' '.join(
                ['{} {} on {}'.format(index, value._tablename(), value._compile_on()) for (index, value) in
                 self.__join__])
        return ''

    def _compile_union(self):
        if self.__union__:
            return ' ' + ' '.join(['{} ({})'.format(index, value.tosql()) for (index, value) in self.__union__])
        return ''

    def _compile_on(self):
        sqlstr = ['{} {} {}'.format(index[0], index[1], index[2]) for index in self.__on__]
        return ' and '.join(sqlstr)

    def _compile_having(self):
        if self.__having__:
            return self.__having__
        return ''

    def _compile_where(self):
        if len(self.__where__) > 0:
            sqlstr = []
            for index in self.__where__:
                if isinstance(index, dict):
                    sqlstr.append(' and '.join(self._compile_dict(index)))
                elif isinstance(index, tuple):
                    sqlstr.append(self._compile_tuple(index))
            return ' where {}'.format(' and '.join(sqlstr))
        return ''

    def _compile_orwhere(self):
        if len(self.__orwhere__) > 0:
            sqlstr = []
            for index in self.__orwhere__:
                if isinstance(index, dict):
                    subsql = self._compile_dict(index)
                    if len(subsql) == 1:
                        sqlstr.append(subsql.pop())
                    else:
                        sqlstr.append('({})'.format(' and '.join(subsql)))
                elif isinstance(index, tuple):
                    sqlstr.append(self._compile_tuple(index))
                elif isinstance(index, list):
                    subsql = []
                    for items in index:
                        if len(items) == 2:
                            subsql.append(self._compile_keyvalue(items[0], items[1]))
                        if len(items) == 3:
                            subsql.append(self._compile_tuple((items[0], items[1], items[2])))
                    sqlstr.append('({})'.format(' and '.join(subsql)))
                else:
                    raise Exception('undefined query condition {}'.format(index.__str__()))
            if len(self.__where__) > 0:
                return ' or {}'.format(' or '.join(sqlstr))
            return ' where {}'.format(' or '.join(sqlstr))
        return ''

    def _compile_whereor(self):
        if len(self.__whereor__) > 0:
            sqlstr = []
            for index in self.__whereor__:
                subsql = []
                for item in index:
                    if isinstance(item, dict):
                        if len(item) == 1:
                            subsql.append(self._compile_dict(item).pop())
                        else:
                            subsql.append('(' + ' and '.join(self._compile_dict(item)) + ')')
                    elif isinstance(item, list):
                        if isinstance(item[0], str):
                            subsql.append(self._compile_tuple(tuple(item)))
                        else:
                            subsql.append(self._compile_lists(item))
                    elif isinstance(item, tuple):
                        subsql.append(self._compile_tuple(item))
                    else:
                        raise Exception('whereor param invalid')
                sqlstr.append(' or '.join(subsql))
            if len(self.__where__) > 0:
                return ' and ({})'.format(' or '.join(sqlstr))
            return ' where ({})'.format(' or '.join(sqlstr))
        return ''

    def _compile_dict(self, data):
        return ['{}={}'.format(expr.format_column(index, self.__model__), expr.format_string(value)) for index, value in
                data.items()]

    def _compile_tuple(self, data):
        if data[1] in ['in', 'not in']:
            return self._compile_in((data[0], data[1], data[2]))
        elif data[1] in ['between', 'not between']:
            return self._compile_between((data[0], data[1], data[2]))
        return '{} {} {}'.format(expr.format_column(data[0], self.__model__), data[1], expr.format_string(data[2]))

    def _compile_in(self, data):
        # 处理空列表的情况
        # 对于 IN 空列表：生成 1=0（不匹配任何行）
        # 对于 NOT IN 空列表：生成 1=1（匹配所有行）
        values = data[2]
        if isinstance(values, (list, tuple)) and len(values) == 0:
            if data[1] == 'in':
                return '1=0'
            else:  # not in
                return '1=1'
        return '{} {} {}'.format(expr.format_column(data[0], self.__model__), data[1], expr.list_to_str(data[2]))

    def _compile_list(self, data):
        length = len(data)
        if length == 2:
            return self._compile_keyvalue(data[0], data[1])
        if length == 3:
            return self._compile_tuple((data[0], data[1], data[2]))

    def _compile_lists(self, data):
        return_data = []
        for index in data:
            if isinstance(index, list):
                return_data.append(self._compile_list(index))
            if isinstance(index, tuple):
                return_data.append(self._compile_tuple(index))
        return '(' + ' and '.join(return_data) + ')'

    def _compile_between(self, data):
        if not (len(data) == 3 and len(data[2]) == 2):
            raise Exception('between param invalid')
        return '{} {} {} and {}'.format(expr.format_column(data[0], self.__model__), data[1],
                                        expr.format_string(data[2][0]),
                                        expr.format_string(data[2][1]))

    def _compile_keyvalue(self, key, value):
        return '{}={}'.format(expr.format_column(key, self.__model__), expr.format_string(value))

    def _compile_subquery(self):
        subquery = []
        for index, value in self.__subquery__:
            if isinstance(value, str):
                subquery.append('{} as {}'.format(value, index))
            else:
                subquery.append('({}) as {}'.format(value.tosql(), index))
        return ','.join(subquery)

    def _compile_duplicate_data(self, keys):
        return ','.join(['`{}` = values(`{}`)'.format(i, i) for i in keys])

    def _get_connection(self):
        return self.connect(self.__model__)

    def _check_columns_value(self, value):
        if self.__subquery__ and len(self.__subquery__) >= 2 and isinstance(value, str):
            tmp = value.split('.')
            if len(tmp) == 2 and tmp[0] in self._get_subquery_alias():
                return Expression(value)
        return value

    def _get_subquery_alias(self):
        return [index for index, value in self.__subquery__]

    def database(self, name):
        self.__model__.__database__ = name
        return self

    def _tablename(self):
        if self.__subquery__:
            return self._compile_subquery()
        if self.__alias__ is None:
            return self.__model__.__tablename__
        return self.__model__.__tablename__ + ' as {}'.format(self.__alias__)

    def _format_columns(self, columns):
        return list(map(lambda index: expr.format_column(index, self.__model__), columns))

    def _set_create_time(self, data):
        currtime = self.__model__.fresh_timestamp()
        update_column = self.__model__.update_time_column()
        create_column = self.__model__.create_time_column()
        for index in data:
            if create_column and create_column not in index:
                index[create_column] = currtime
            if update_column and update_column not in index:
                index[update_column] = currtime
        return data

    def _set_update_time(self, data):
        currtime = self.__model__.fresh_timestamp()
        update_column = self.__model__.update_time_column()
        if update_column and update_column not in data:
            data[update_column] = currtime
        return data

    def _set_crease_update_time(self, data):
        currtime = self.__model__.fresh_timestamp()
        update_column = self.__model__.update_time_column()
        if update_column and update_column not in data:
            data[update_column] = "'{}'".format(currtime)
        return data

    def transaction(self, callback):
        return self._get_connection().transaction(callback)

    def transaction_wrapper(self, callback):
        return self._get_connection().transaction_wrapper(callback)
